Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-service_identity for openSUSE:Factory checked in at 2024-11-01 21:00:44 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-service_identity (Old) and /work/SRC/openSUSE:Factory/.python-service_identity.new.2020 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-service_identity" Fri Nov 1 21:00:44 2024 rev:16 rq:1219726 version:24.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-service_identity/python-service_identity.changes 2024-01-21 23:07:26.671511362 +0100 +++ /work/SRC/openSUSE:Factory/.python-service_identity.new.2020/python-service_identity.changes 2024-11-01 21:00:48.745507595 +0100 @@ -1,0 +2,11 @@ +Wed Oct 30 19:54:23 UTC 2024 - Dirk Müller <dmueller@suse.com> + +- update to 24.2.0: + * Python 3.13 is now officially supported. + * pyOpenSSL's identity extraction has been reimplemented using + *cryptography*'s primitives instead of deprecated pyOpenSSL + APIs. + * As a result, the oldest supported pyOpenSSL version is now + 17.1.0. + +------------------------------------------------------------------- Old: ---- 24.1.0.tar.gz New: ---- 24.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-service_identity.spec ++++++ --- /var/tmp/diff_new_pack.89EqKV/_old 2024-11-01 21:00:49.505539320 +0100 +++ /var/tmp/diff_new_pack.89EqKV/_new 2024-11-01 21:00:49.509539487 +0100 @@ -19,7 +19,7 @@ %define oname service_identity %{?sle15_python_module_pythons} Name: python-service_identity -Version: 24.1.0 +Version: 24.2.0 Release: 0 Summary: Service identity verification for pyOpenSSL License: MIT @@ -34,7 +34,7 @@ BuildRequires: %{python_module hatchling >= 1.14.0} BuildRequires: %{python_module idna} BuildRequires: %{python_module pip} -BuildRequires: %{python_module pyOpenSSL >= 17.0.0} +BuildRequires: %{python_module pyOpenSSL >= 17.1.0} BuildRequires: %{python_module pyasn1-modules} BuildRequires: %{python_module pyasn1} BuildRequires: %{python_module pytest} @@ -46,7 +46,7 @@ Requires: python-pyasn1 Requires: python-pyasn1-modules Recommends: python-idna -Recommends: python-pyOpenSSL +Recommends: python-pyOpenSSL >= 17.1.0 BuildArch: noarch %ifpython2 Requires: python-ipaddress ++++++ 24.1.0.tar.gz -> 24.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/.git_archival.txt new/service-identity-24.2.0/.git_archival.txt --- old/service-identity-24.1.0/.git_archival.txt 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/.git_archival.txt 2024-10-26 09:10:38.000000000 +0200 @@ -1,4 +1,3 @@ -node: e5ba15ec13b1750cae35004dede2e6e4af74308f -node-date: 2024-01-14T08:05:19+01:00 -describe-name: 24.1.0 -ref-names: tag: 24.1.0 +node: b51873e5aff777ff2e35dfb2bee1f4acbf203b25 +node-date: 2024-10-26T09:10:38+02:00 +describe-name: 24.2.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/.github/CONTRIBUTING.md new/service-identity-24.2.0/.github/CONTRIBUTING.md --- old/service-identity-24.1.0/.github/CONTRIBUTING.md 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/.github/CONTRIBUTING.md 2024-10-26 09:10:38.000000000 +0200 @@ -137,8 +137,10 @@ A very important return value. """ ``` + - If you add or change public APIs, tag the docstring using `.. versionadded:: 16.0.0 WHAT` or `.. versionchanged:: 16.2.0 WHAT`. -- We use [Ruff](https://ruff.rs/) to sort our imports, and we use [Black](https://github.com/psf/black) with line length of 79 characters to format our code. + +- We follow [PEP 8](https://peps.python.org/pep-0008/) as enforced by [Ruff](https://ruff.rs/) with a line length of 79 characters. As long as you run our full [*tox*] suite before committing, or install our [*pre-commit*] hooks (ideally you'll do both – see [*Local Development Environment*](#local-development-environment) above), you won't have to spend any time on formatting your code at all. If you don't, [CI] will catch it for you – but that seems like a waste of your time! diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/.github/SECURITY.md new/service-identity-24.2.0/.github/SECURITY.md --- old/service-identity-24.1.0/.github/SECURITY.md 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/.github/SECURITY.md 2024-10-26 09:10:38.000000000 +0200 @@ -2,7 +2,7 @@ ## Supported Versions -We are following [*CalVer*](https://calver.org) with generous backwards-compatibility guarantees. +We are following [Calendar Versioning](https://calver.org) with generous backwards-compatibility guarantees. *Therefore we only support the latest version*. That said, you shouldn't be afraid to upgrade if you're only using our documented public APIs and pay attention to `DeprecationWarning`s. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/.github/workflows/ci.yml new/service-identity-24.2.0/.github/workflows/ci.yml --- old/service-identity-24.1.0/.github/workflows/ci.yml 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/.github/workflows/ci.yml 2024-10-26 09:10:38.000000000 +0200 @@ -4,6 +4,7 @@ on: push: branches: [main] + tags: ["*"] pull_request: workflow_dispatch: @@ -14,6 +15,7 @@ permissions: {} + jobs: lint: name: Run linters @@ -22,61 +24,81 @@ - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.11" # XXX: change once interrogate works on 3.12 - cache: pip + python-version-file: .python-version-default + - uses: hynek/setup-cached-uv@v2 + + - run: > + uvx --with tox-uv + tox run -e lint -- --show-diff-on-failure + + + build-package: + name: Build & verify package + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: hynek/build-and-inspect-python-package@v2 + id: baipp + + outputs: + # Used to define the matrix for tests below. The value is based on + # packaging metadata (trove classifiers). + python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }} - - name: Install & run tox - run: | - python -Im pip install tox - python -Im tox run -e lint -- --show-diff-on-failure tests: - name: Tests on ${{ matrix.python-version }} + name: Tests & Mypy API on ${{ matrix.python-version }} runs-on: ubuntu-latest + needs: build-package strategy: matrix: - python-version: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" - - "pypy-3.8" - - "pypy-3.9" + # Created by the build-and-inspect-python-package action above. + python-version: ${{ fromJson(needs.build-package.outputs.python-versions) }} steps: - - uses: actions/checkout@v4 + - name: Download pre-built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - run: | + tar xf dist/*.tar.gz --strip-components=1 + rm -rf src - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - cache: pip - - - name: Prepare tox & run tests - run: | - V=${{ matrix.python-version }} + - uses: hynek/setup-cached-uv@v2 - if [[ "$V" = pypy-* ]]; then - V=pypy3 - else - V=py$(echo $V | tr -d .) - fi - - python -Im pip install tox - python -Im tox run -f "$V" - - - name: Run Mypy on API - run: python -Im tox run -e mypy-api + - name: Run tests + run: > + uvx --with tox-uv + tox run + --installpkg dist/*.whl + -f py$(echo ${{ matrix.python-version }} | tr -d .) - name: Upload coverage data uses: actions/upload-artifact@v4 with: name: coverage-data-${{ matrix.python-version }} path: .coverage.* + include-hidden-files: true if-no-files-found: ignore + - name: Check public API with Mypy + run: > + uvx --with tox-uv + tox run + --installpkg dist/*.whl + -e mypy-api + + coverage: - name: Combine & check coverage + name: Ensure 100% test coverage needs: tests runs-on: ubuntu-latest @@ -85,7 +107,7 @@ - uses: actions/setup-python@v5 with: python-version-file: .python-version-default - cache: pip + - uses: hynek/setup-cached-uv@v2 - uses: actions/download-artifact@v4 with: @@ -94,16 +116,16 @@ - name: Combine coverage & fail if it's <100% run: | - python -Im pip install coverage[toml] + uv tool install coverage[toml] - python -Im coverage combine - python -Im coverage html --skip-covered --skip-empty + coverage combine + coverage html --skip-covered --skip-empty # Report and write to summary. - python -Im coverage report --format=markdown >> $GITHUB_STEP_SUMMARY + coverage report --format=markdown >> $GITHUB_STEP_SUMMARY # Report again and fail if under 100%. - python -Im coverage report --fail-under=100 + coverage report --fail-under=100 - name: Upload HTML report if check failed. uses: actions/upload-artifact@v4 @@ -112,21 +134,28 @@ path: htmlcov if: ${{ failure() }} + mypy-pkg: - name: Type-check package + name: Mypy Codebase runs-on: ubuntu-latest + needs: build-package steps: - - uses: actions/checkout@v4 + - name: Download pre-built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - run: tar xf dist/*.tar.gz --strip-components=1 - uses: actions/setup-python@v5 with: python-version-file: .python-version-default - cache: pip + - uses: hynek/setup-cached-uv@v2 + + - run: > + uvx --with tox-uv + tox run -e mypy-pkg - - name: Install & run tox - run: | - python -Im pip install tox - python -Im tox run -e mypy-pkg install-dev: strategy: @@ -147,25 +176,35 @@ run: | python -Im pip install -e .[dev] python -Ic 'import service_identity; print(service_identity.__version__)' + python -Ic 'import service_identity.pyopenssl' + python -Ic 'import service_identity.cryptography' + docs: name: Build docs & run doctests + needs: build-package runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v4 + - name: Download pre-built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - run: tar xf dist/*.tar.gz --strip-components=1 - uses: actions/setup-python@v5 with: # Keep in sync with tox.ini/docs & .readthedocs.yaml python-version: "3.12" - cache: pip + - uses: hynek/setup-cached-uv@v2 + + - run: > + uvx --with tox-uv + tox run -e docs - - name: Install & run tox - run: | - python -Im pip install tox - python -Im tox run -e docs - # Ensure everything required is passing for branch protection. required-checks-pass: + name: Ensure everything required is passing for branch protection if: always() needs: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/.github/workflows/pypi-package.yml new/service-identity-24.2.0/.github/workflows/pypi-package.yml --- old/service-identity-24.1.0/.github/workflows/pypi-package.yml 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/.github/workflows/pypi-package.yml 2024-10-26 09:10:38.000000000 +0200 @@ -5,13 +5,13 @@ push: branches: [main] tags: ["*"] - pull_request: release: types: - published workflow_dispatch: permissions: + attestations: write contents: read id-token: write @@ -27,6 +27,8 @@ fetch-depth: 0 - uses: hynek/build-and-inspect-python-package@v2 + with: + attest-build-provenance-github: 'true' # Upload to Test PyPI on every commit on main. release-test-pypi: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/.pre-commit-config.yaml new/service-identity-24.2.0/.pre-commit-config.yaml --- old/service-identity-24.1.0/.pre-commit-config.yaml 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/.pre-commit-config.yaml 2024-10-26 09:10:38.000000000 +0200 @@ -1,31 +1,26 @@ --- repos: - - repo: https://github.com/psf/black - rev: 23.12.1 - hooks: - - id: black - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.13 + rev: v0.7.1 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell args: [-L, fo] - repo: https://github.com/econchick/interrogate - rev: 1.5.0 + rev: 1.7.0 hooks: - id: interrogate - language_version: python3.11 - args: [tests, -v] + args: [tests] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/.python-version-default new/service-identity-24.2.0/.python-version-default --- old/service-identity-24.1.0/.python-version-default 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/.python-version-default 2024-10-26 09:10:38.000000000 +0200 @@ -1 +1 @@ -3.12 +3.13 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/.readthedocs.yaml new/service-identity-24.2.0/.readthedocs.yaml --- old/service-identity-24.1.0/.readthedocs.yaml 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/.readthedocs.yaml 2024-10-26 09:10:38.000000000 +0200 @@ -2,10 +2,14 @@ version: 2 build: - os: ubuntu-22.04 + os: ubuntu-lts-latest tools: # Keep in-sync with tox.ini/docs and ci.yml/docs python: "3.12" + jobs: + # Need the tags to calculate the version (sometimes). + post_checkout: + - git fetch --tags python: install: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/CHANGELOG.md new/service-identity-24.2.0/CHANGELOG.md --- old/service-identity-24.1.0/CHANGELOG.md 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/CHANGELOG.md 2024-10-26 09:10:38.000000000 +0200 @@ -13,6 +13,21 @@ <!-- changelog follows --> +## [24.2.0](https://github.com/pyca/service-identity/compare/24.1.0...24.2.0) - 2024-10-26 + +### Added + +- Python 3.13 is now officially supported. + [#74](https://github.com/pyca/service-identity/pull/74) + + +### Changed + +- pyOpenSSL's identity extraction has been reimplemented using *cryptography*'s primitives instead of deprecated pyOpenSSL APIs. + As a result, the oldest supported pyOpenSSL version is now 17.1.0. + [#70](https://github.com/pyca/service-identity/pull/70) + + ## [24.1.0](https://github.com/pyca/service-identity/compare/23.1.0...24.1.0) - 2024-01-14 ### Changed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/README.md new/service-identity-24.2.0/README.md --- old/service-identity-24.1.0/README.md 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/README.md 2024-10-26 09:10:38.000000000 +0200 @@ -36,8 +36,7 @@ ### *service-identity* for Enterprise -Available as part of the Tidelift Subscription. +Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek). The maintainers of *service-identity* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open-source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. -[Learn more.](https://tidelift.com/lifter/search/pypi/service-identity) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/docs/conf.py new/service-identity-24.2.0/docs/conf.py --- old/service-identity-24.1.0/docs/conf.py 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/docs/conf.py 2024-10-26 09:10:38.000000000 +0200 @@ -1,16 +1,6 @@ from importlib import metadata -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. @@ -45,9 +35,6 @@ # The suffix of source filenames. source_suffix = ".rst" -# The encoding of source files. -# source_encoding = 'utf-8-sig' - # The master toctree document. master_doc = "index" @@ -68,50 +55,16 @@ if "dev" in release: release = version = "UNRELEASED" -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -# pygments_style = "sphinx" - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - # -- Options for HTML output ---------------------------------------------- html_theme = "furo" html_theme_options = { - "top_of_page_button": None, + "top_of_page_buttons": [], "light_css_variables": { "font-stack": "Inter, sans-serif", "font-stack--monospace": "BerkeleyMono, MonoLisa, ui-monospace, " @@ -142,27 +95,6 @@ ) ] -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples @@ -177,10 +109,6 @@ ) ] -# If true, show URL addresses after external links. -# man_show_urls = False - - # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples @@ -198,18 +126,6 @@ ) ] -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False - intersphinx_mapping = { "python": ("https://docs.python.org/3", None), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/pyproject.toml new/service-identity-24.2.0/pyproject.toml --- old/service-identity-24.1.0/pyproject.toml 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/pyproject.toml 2024-10-26 09:10:38.000000000 +0200 @@ -18,6 +18,7 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Security :: Cryptography", @@ -88,12 +89,8 @@ addopts = ["-ra", "--strict-markers", "--strict-config"] xfail_strict = true testpaths = "tests" -filterwarnings = [ - "once::Warning", - "ignore:::aiohttp[.*]", - "ignore:::importlib[.*]", - "ignore::DeprecationWarning:twisted.python.threadable", -] +filterwarnings = ["once::Warning"] +norecursedirs = ["tests/typing"] [tool.coverage.run] @@ -132,55 +129,56 @@ whitelist-regex = ["test_.*"] -[tool.black] -line-length = 79 - - [tool.ruff] src = ["src", "tests"] -select = [ - "E", # pycodestyle - "W", # pycodestyle - "F", # Pyflakes - "UP", # pyupgrade - "N", # pep8-naming - "YTT", # flake8-2020 - "S", # flake8-bandit - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "T10", # flake8-debugger - "ISC", # flake8-implicit-str-concat - "RET", # flake8-return - "SIM", # flake8-simplify - "DTZ", # flake8-datetimez - "I", # isort - "PGH", # pygrep-hooks - "PLC", # Pylint - "PIE", # flake8-pie - "RUF", # ruff -] +line-length = 79 + +[tool.ruff.lint] +select = ["ALL"] ignore = [ - "RUF001", # leave my smart characters alone - "N801", # some artistic freedom when naming things after RFCs - "N802", # ditto + "A001", # shadowing is fine + "ANN", # Mypy is better at this + "ARG001", # we don't control all args passed in + "ARG005", # we need stub lambdas + "COM", # ruff format takes care of our commas + "D", # We prefer our own docstring style. + "E501", # leave line-length enforcement to ruff format + "FIX", # Yes, we want XXX as a marker. + "INP001", # sometimes we want Python files outside of packages + "ISC001", # conflicts with ruff format + "N801", # some artistic freedom when naming things after RFCs + "N802", # ditto + "PLR2004", # numbers are sometimes fine + "RUF001", # leave my smart characters alone + "SLF001", # private members are accessed by friendly functions + "TCH", # TYPE_CHECKING blocks break autodocs + "TD", # we don't follow other people's todo style ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "tests/*" = [ + "B018", # "useless" expressions can be useful in tests + "PLC1901", # empty strings are falsey, but are less specific in tests + "PT005", # we always add underscores and explicit name + "PT011", # broad is fine "S101", # assert "S301", # I know pickle is bad, but people use it. - "SIM300", # Yoda rocks in tests - "PLC1901", # empty strings are falsey, but are less specific in tests - "B018", # "useless" expressions can be useful in tests + "SIM300", # Yoda rocks in asserts + "TRY301", # tests need to raise exceptions +] +"docs/pyopenssl_example.py" = [ + "T201", # print is fine in the example + "T203", # pprint is fine in the example ] -[tool.ruff.isort] +[tool.ruff.lint.isort] lines-between-types = 1 lines-after-imports = 2 [tool.mypy] strict = true +pretty = true show_error_codes = true enable_error_code = ["ignore-without-code"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/src/service_identity/__init__.py new/service-identity-24.2.0/src/service_identity/__init__.py --- old/service-identity-24.1.0/src/service_identity/__init__.py 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/src/service_identity/__init__.py 2024-10-26 09:10:38.000000000 +0200 @@ -2,7 +2,6 @@ Verify service identities. """ - from . import cryptography, hazmat, pyopenssl from .exceptions import ( CertificateError, @@ -30,34 +29,10 @@ def __getattr__(name: str) -> str: - dunder_to_metadata = { - "__version__": "version", - "__description__": "summary", - "__uri__": "", - "__url__": "", - "__email__": "", - } - if name not in dunder_to_metadata: - raise AttributeError(f"module {__name__} has no attribute {name}") - - import warnings - - from importlib.metadata import metadata - - warnings.warn( - f"Accessing service_identity.{name} is deprecated and will be " - "removed in a future release. Use importlib.metadata directly " - "to query packaging metadata.", - DeprecationWarning, - stacklevel=2, - ) - - meta = metadata("service-identity") - - if name in ("__uri__", "__url__"): - return meta["Project-URL"].split(" ", 1)[1] + if name != "__version__": + msg = f"module {__name__} has no attribute {name}" + raise AttributeError(msg) - if name == "__email__": - return meta["Author-email"].split("<", 1)[1].rstrip(">") + from importlib.metadata import version - return meta[dunder_to_metadata[name]] + return version("service-identity") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/src/service_identity/cryptography.py new/service-identity-24.2.0/src/service_identity/cryptography.py --- old/service-identity-24.1.0/src/service_identity/cryptography.py 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/src/service_identity/cryptography.py 2024-10-26 09:10:38.000000000 +0200 @@ -163,7 +163,8 @@ if isinstance(srv, IA5String): ids.append(SRVPattern.from_bytes(srv.asOctets())) else: # pragma: no cover - raise CertificateError("Unexpected certificate content.") + msg = "Unexpected certificate content." + raise CertificateError(msg) return ids diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/src/service_identity/hazmat.py new/service-identity-24.2.0/src/service_identity/hazmat.py --- old/service-identity-24.1.0/src/service_identity/hazmat.py 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/src/service_identity/hazmat.py 2024-10-26 09:10:38.000000000 +0200 @@ -51,9 +51,8 @@ if a pattern of the respective type is present. """ if not cert_patterns: - raise CertificateError( - "Certificate does not contain any `subjectAltName`s." - ) + msg = "Certificate does not contain any `subjectAltName`s." + raise CertificateError(msg) errors = [] matches = _find_matches(cert_patterns, obligatory_ids) + _find_matches( @@ -63,7 +62,9 @@ matched_ids = [match.service_id for match in matches] for i in obligatory_ids: if i not in matched_ids: - errors.append(i.error_on_mismatch(mismatched_id=i)) + errors.append( # noqa: PERF401 + i.error_on_mismatch(mismatched_id=i) + ) for i in optional_ids: # If an optional ID is not matched by a certificate pattern *but* there @@ -73,7 +74,9 @@ if i not in matched_ids and _contains_instance_of( cert_patterns, i.pattern_class ): - errors.append(i.error_on_mismatch(mismatched_id=i)) + errors.append( # noqa: PERF401 + i.error_on_mismatch(mismatched_id=i) + ) if errors: raise VerificationError(errors=errors) @@ -95,7 +98,10 @@ for sid in service_ids: for cid in cert_patterns: if sid.verify(cid): - matches.append(ServiceMatch(cert_pattern=cid, service_id=sid)) + matches.append( # noqa: PERF401 + ServiceMatch(cert_pattern=cid, service_id=sid) + ) + return matches @@ -121,9 +127,10 @@ try: int(pattern) - return True except ValueError: pass + else: + return True try: ipaddress.ip_address(pattern.replace("*", "1")) @@ -147,12 +154,14 @@ @classmethod def from_bytes(cls, pattern: bytes) -> DNSPattern: if not isinstance(pattern, bytes): - raise TypeError("The DNS pattern must be a bytes string.") + msg = "The DNS pattern must be a bytes string." + raise TypeError(msg) pattern = pattern.strip() if pattern == b"" or _is_ip_address(pattern) or b"\0" in pattern: - raise CertificateError(f"Invalid DNS pattern {pattern!r}.") + msg = f"Invalid DNS pattern {pattern!r}." + raise CertificateError(msg) pattern = pattern.translate(_TRANS_TO_LOWER) if b"*" in pattern: @@ -175,9 +184,8 @@ try: return cls(pattern=ipaddress.ip_address(bs)) except ValueError: - raise CertificateError( - f"Invalid IP address pattern {bs!r}." - ) from None + msg = f"Invalid IP address pattern {bs!r}." + raise CertificateError(msg) from None @attr.s(slots=True) @@ -194,12 +202,14 @@ @classmethod def from_bytes(cls, pattern: bytes) -> URIPattern: if not isinstance(pattern, bytes): - raise TypeError("The URI pattern must be a bytes string.") + msg = "The URI pattern must be a bytes string." + raise TypeError(msg) pattern = pattern.strip().translate(_TRANS_TO_LOWER) if b":" not in pattern or b"*" in pattern or _is_ip_address(pattern): - raise CertificateError(f"Invalid URI pattern {pattern!r}.") + msg = f"Invalid URI pattern {pattern!r}." + raise CertificateError(msg) protocol_pattern, hostname = pattern.split(b":") @@ -223,7 +233,8 @@ @classmethod def from_bytes(cls, pattern: bytes) -> SRVPattern: if not isinstance(pattern, bytes): - raise TypeError("The SRV pattern must be a bytes string.") + msg = "The SRV pattern must be a bytes string." + raise TypeError(msg) pattern = pattern.strip().translate(_TRANS_TO_LOWER) @@ -233,7 +244,8 @@ or b"*" in pattern or _is_ip_address(pattern) ): - raise CertificateError(f"Invalid SRV pattern {pattern!r}.") + msg = f"Invalid SRV pattern {pattern!r}." + raise CertificateError(msg) name, hostname = pattern.split(b".", 1) return cls( @@ -253,15 +265,12 @@ @runtime_checkable class ServiceID(Protocol): @property - def pattern_class(self) -> type[CertificatePattern]: - ... + def pattern_class(self) -> type[CertificatePattern]: ... @property - def error_on_mismatch(self) -> type[Mismatch]: - ... + def error_on_mismatch(self) -> type[Mismatch]: ... - def verify(self, pattern: CertificatePattern) -> bool: - ... + def verify(self, pattern: CertificatePattern) -> bool: ... @attr.s(init=False, slots=True) @@ -279,25 +288,27 @@ def __init__(self, hostname: str): if not isinstance(hostname, str): - raise TypeError("DNS-ID must be a text string.") + msg = "DNS-ID must be a text string." + raise TypeError(msg) hostname = hostname.strip() if not hostname or _is_ip_address(hostname): - raise ValueError("Invalid DNS-ID.") + msg = "Invalid DNS-ID." + raise ValueError(msg) if any(ord(c) > 127 for c in hostname): if idna: ascii_id = idna.encode(hostname) else: - raise ImportError( - "idna library is required for non-ASCII IDs." - ) + msg = "idna library is required for non-ASCII IDs." + raise ImportError(msg) else: ascii_id = hostname.encode("ascii") self.hostname = ascii_id.translate(_TRANS_TO_LOWER) if self._RE_LEGAL_CHARS.match(self.hostname) is None: - raise ValueError("Invalid DNS-ID.") + msg = "Invalid DNS-ID." + raise ValueError(msg) def verify(self, pattern: CertificatePattern) -> bool: """ @@ -346,11 +357,13 @@ def __init__(self, uri: str): if not isinstance(uri, str): - raise TypeError("URI-ID must be a text string.") + msg = "URI-ID must be a text string." + raise TypeError(msg) uri = uri.strip() if ":" not in uri or _is_ip_address(uri): - raise ValueError("Invalid URI-ID.") + msg = "Invalid URI-ID." + raise ValueError(msg) prot, hostname = uri.split(":") @@ -384,11 +397,13 @@ def __init__(self, srv: str): if not isinstance(srv, str): - raise TypeError("SRV-ID must be a text string.") + msg = "SRV-ID must be a text string." + raise TypeError(msg) srv = srv.strip() if "." not in srv or _is_ip_address(srv) or srv[0] != "_": - raise ValueError("Invalid SRV-ID.") + msg = "Invalid SRV-ID." + raise ValueError(msg) name, hostname = srv.split(".", 1) @@ -420,7 +435,7 @@ if actual_head.startswith(b"xn--"): return False - return cert_head == b"*" or cert_head == actual_head + return cert_head in (b"*", actual_head) return cert_pattern == actual_hostname @@ -432,25 +447,19 @@ """ cnt = cert_pattern.count(b"*") if cnt > 1: - raise CertificateError( - f"Certificate's DNS-ID {cert_pattern!r} contains too many wildcards." - ) + msg = f"Certificate's DNS-ID {cert_pattern!r} contains too many wildcards." + raise CertificateError(msg) parts = cert_pattern.split(b".") if len(parts) < 3: - raise CertificateError( - f"Certificate's DNS-ID {cert_pattern!r} has too few host components for " - "wildcard usage." - ) + msg = f"Certificate's DNS-ID {cert_pattern!r} has too few host components for wildcard usage." + raise CertificateError(msg) # We assume there will always be only one wildcard allowed. if b"*" not in parts[0]: - raise CertificateError( - "Certificate's DNS-ID {!r} has a wildcard outside the left-most " - "part.".format(cert_pattern) - ) + msg = f"Certificate's DNS-ID {cert_pattern!r} has a wildcard outside the left-most part." + raise CertificateError(msg) if any(not len(p) for p in parts): - raise CertificateError( - f"Certificate's DNS-ID {cert_pattern!r} contains empty parts." - ) + msg = f"Certificate's DNS-ID {cert_pattern!r} contains empty parts." + raise CertificateError(msg) # Ensure no locale magic interferes. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/src/service_identity/pyopenssl.py new/service-identity-24.2.0/src/service_identity/pyopenssl.py --- old/service-identity-24.1.0/src/service_identity/pyopenssl.py 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/src/service_identity/pyopenssl.py 2024-10-26 09:10:38.000000000 +0200 @@ -9,20 +9,11 @@ from typing import Sequence -from pyasn1.codec.der.decoder import decode -from pyasn1.type.char import IA5String -from pyasn1.type.univ import ObjectIdentifier -from pyasn1_modules.rfc2459 import GeneralNames - -from .exceptions import CertificateError +from .cryptography import extract_patterns as _cryptography_extract_patterns from .hazmat import ( DNS_ID, CertificatePattern, - DNSPattern, IPAddress_ID, - IPAddressPattern, - SRVPattern, - URIPattern, verify_service_identity, ) @@ -105,9 +96,6 @@ ) -ID_ON_DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") # id_on_dnsSRV - - def extract_patterns(cert: X509) -> Sequence[CertificatePattern]: """ Extract all valid ID patterns from a certificate for service verification. @@ -121,44 +109,7 @@ .. versionchanged:: 23.1.0 ``commonName`` is not used as a fallback anymore. """ - ids: list[CertificatePattern] = [] - for i in range(cert.get_extension_count()): - ext = cert.get_extension(i) - if ext.get_short_name() == b"subjectAltName": - names, _ = decode(ext.get_data(), asn1Spec=GeneralNames()) - for n in names: - name_string = n.getName() - if name_string == "dNSName": - ids.append( - DNSPattern.from_bytes(n.getComponent().asOctets()) - ) - elif name_string == "iPAddress": - ids.append( - IPAddressPattern.from_bytes( - n.getComponent().asOctets() - ) - ) - elif name_string == "uniformResourceIdentifier": - ids.append( - URIPattern.from_bytes(n.getComponent().asOctets()) - ) - elif name_string == "otherName": - comp = n.getComponent() - oid = comp.getComponentByPosition(0) - if oid == ID_ON_DNS_SRV: - srv, _ = decode(comp.getComponentByPosition(1)) - if isinstance(srv, IA5String): - ids.append(SRVPattern.from_bytes(srv.asOctets())) - else: # pragma: no cover - raise CertificateError( - "Unexpected certificate content." - ) - else: # pragma: no cover - pass - else: # pragma: no cover - pass - - return ids + return _cryptography_extract_patterns(cert.to_cryptography()) def extract_ids(cert: X509) -> Sequence[CertificatePattern]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/tests/constraints/oldest-pyopenssl.txt new/service-identity-24.2.0/tests/constraints/oldest-pyopenssl.txt --- old/service-identity-24.1.0/tests/constraints/oldest-pyopenssl.txt 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/tests/constraints/oldest-pyopenssl.txt 2024-10-26 09:10:38.000000000 +0200 @@ -1,3 +1,3 @@ attrs==19.1.0 cryptography<35 -pyOpenSSL==17.0.0 +pyOpenSSL==17.1.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/tests/test_hazmat.py new/service-identity-24.2.0/tests/test_hazmat.py --- old/service-identity-24.1.0/tests/test_hazmat.py 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/tests/test_hazmat.py 2024-10-26 09:10:38.000000000 +0200 @@ -699,11 +699,11 @@ """ The __str__ and __repr__ methods return something helpful. """ - try: + with pytest.raises(VerificationError) as ei: raise VerificationError(errors=["foo"]) - except VerificationError as e: - assert repr(e) == str(e) - assert str(e) != "" + + assert repr(ei.value) == str(ei.value) + assert str(ei.value) != "" @pytest.mark.parametrize("proto", range(pickle.HIGHEST_PROTOCOL + 1)) @pytest.mark.parametrize( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/tests/test_packaging.py new/service-identity-24.2.0/tests/test_packaging.py --- old/service-identity-24.1.0/tests/test_packaging.py 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/tests/test_packaging.py 2024-10-26 09:10:38.000000000 +0200 @@ -10,38 +10,10 @@ """ service_identity.__version__ returns the correct version. """ - with pytest.deprecated_call(): - assert ( - metadata.version("service-identity") - == service_identity.__version__ - ) - - def test_description(self): - """ - service_identity.__description__ returns the correct description. - """ - with pytest.deprecated_call(): - assert ( - "Service identity verification for pyOpenSSL & cryptography." - == service_identity.__description__ - ) - - @pytest.mark.parametrize("name", ["uri", "url"]) - def test_uri(self, name): - """ - service_identity.__uri__ & __url__ return the correct project URL. - """ - with pytest.deprecated_call(): - assert "https://service-identity.readthedocs.io/" == getattr( - service_identity, f"__{name}__" - ) - - def test_email(self): - """ - service_identity.__email__ returns Hynek's email address. - """ - with pytest.deprecated_call(): - assert "hs@ox.cx" == service_identity.__email__ + assert ( + metadata.version("service-identity") + == service_identity.__version__ + ) def test_does_not_exist(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/tests/typing/api.py new/service-identity-24.2.0/tests/typing/api.py --- old/service-identity-24.1.0/tests/typing/api.py 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/tests/typing/api.py 2024-10-26 09:10:38.000000000 +0200 @@ -20,9 +20,9 @@ backend = default_backend() c_cert = load_pem_x509_certificate("foo.pem", backend) -c_ids: Sequence[ - service_identity.hazmat.CertificatePattern -] = service_identity.cryptography.extract_patterns(c_cert) +c_ids: Sequence[service_identity.hazmat.CertificatePattern] = ( + service_identity.cryptography.extract_patterns(c_cert) +) service_identity.cryptography.verify_certificate_hostname( c_cert, "example.com" ) @@ -36,8 +36,8 @@ p_cert = conn.get_peer_certificate() assert p_cert -p_ids: Sequence[ - service_identity.hazmat.CertificatePattern -] = service_identity.pyopenssl.extract_patterns(p_cert) +p_ids: Sequence[service_identity.hazmat.CertificatePattern] = ( + service_identity.pyopenssl.extract_patterns(p_cert) +) service_identity.pyopenssl.verify_hostname(conn, "example.com") service_identity.pyopenssl.verify_ip_address(conn, "127.0.0.1") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/service-identity-24.1.0/tox.ini new/service-identity-24.2.0/tox.ini --- old/service-identity-24.1.0/tox.ini 2024-01-14 08:05:19.000000000 +0100 +++ new/service-identity-24.2.0/tox.ini 2024-10-26 09:10:38.000000000 +0200 @@ -4,8 +4,7 @@ lint, mypy-{api,pkg}, docs, - pypy3{,-pyopenssl-latest-idna}, - py3{8,9,10,11,12}{,-pyopenssl}{,-oldest}{,-idna}, + py3{8,9,10,11,12,13}{,-pyopenssl}{,-oldest}{,-idna}, coverage-report @@ -40,7 +39,7 @@ [testenv:lint] skip_install = true -deps = pre-commit +deps = pre-commit-uv commands = pre-commit run --all-files {posargs}