Skip to content

Commit

Permalink
Add support for PyPy 3.10 (#113)
Browse files Browse the repository at this point in the history
* Add support for PyPy 3.10

* Work-around PyPy bug

* Update trove classifiers

---------

Co-authored-by: David Glick <david@glicksoftware.com>
  • Loading branch information
sethmlarson and davisagli authored Sep 7, 2023
1 parent 795d764 commit 2f3c9ba
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 8 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
python-version: ["3.10", "3.11", "3.12-dev"]
python-version: ["3.10", "3.11", "3.12", "pypy3.10"]

runs-on: ${{ matrix.os }}
name: ${{ fromJson('{"macos-latest":"macOS","windows-latest":"Windows","ubuntu-latest":"Ubuntu"}')[matrix.os] }} Python ${{ matrix.python-version }}
Expand All @@ -49,9 +49,10 @@ jobs:
uses: actions/checkout@v3

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- name: Setup Go
uses: actions/setup-go@v3
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ authors = [
readme = "README.md"
license = {file = "LICENSE"}
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: MacOS",
Expand All @@ -21,6 +21,8 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dynamic = ["version", "description"]
requires-python = ">= 3.10"
Expand Down
18 changes: 15 additions & 3 deletions src/truststore/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

import _ssl # type: ignore[import]

from ._ssl_constants import _original_SSLContext, _original_super_SSLContext
from ._ssl_constants import (
_original_SSLContext,
_original_super_SSLContext,
_truststore_SSLContext_dunder_class,
_truststore_SSLContext_super_class,
)

if platform.system() == "Windows":
from ._windows import _configure_context, _verify_peercerts_impl
Expand Down Expand Up @@ -49,9 +54,16 @@ def extract_from_ssl() -> None:
pass


class SSLContext(ssl.SSLContext):
class SSLContext(_truststore_SSLContext_super_class): # type: ignore[misc]
"""SSLContext API that uses system certificates on all platforms"""

@property # type: ignore[misc]
def __class__(self) -> type:
# Dirty hack to get around isinstance() checks
# for ssl.SSLContext instances in aiohttp/trustme
# when using non-CPython implementations.
return _truststore_SSLContext_dunder_class or SSLContext

def __init__(self, protocol: int = None) -> None: # type: ignore[assignment]
self._ctx = _original_SSLContext(protocol)

Expand Down Expand Up @@ -240,7 +252,7 @@ def protocol(self) -> ssl._SSLMethod:
return self._ctx.protocol

@property
def security_level(self) -> int: # type: ignore[override]
def security_level(self) -> int:
return self._ctx.security_level

@property
Expand Down
19 changes: 19 additions & 0 deletions src/truststore/_ssl_constants.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import ssl
import sys
import typing

# Hold on to the original class so we can create it consistently
# even if we inject our own SSLContext into the ssl module.
_original_SSLContext = ssl.SSLContext
_original_super_SSLContext = super(_original_SSLContext, _original_SSLContext)

# CPython is known to be good, but non-CPython implementations
# may implement SSLContext differently so to be safe we don't
# subclass the SSLContext.

# This is returned by truststore.SSLContext.__class__()
_truststore_SSLContext_dunder_class: typing.Optional[type]

# This value is the superclass of truststore.SSLContext.
_truststore_SSLContext_super_class: type

if sys.implementation.name == "cpython":
_truststore_SSLContext_super_class = _original_SSLContext
_truststore_SSLContext_dunder_class = None
else:
_truststore_SSLContext_super_class = object
_truststore_SSLContext_dunder_class = _original_SSLContext


def _set_ssl_context_verify_mode(
ssl_context: ssl.SSLContext, verify_mode: ssl.VerifyMode
Expand Down
8 changes: 6 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,13 @@ async def handler(request: web.Request) -> web.Response:
app.add_routes([web.get("/", handler)])

ctx = original_SSLContext(ssl.PROTOCOL_TLS_SERVER)

# We use str(pathlib.Path) here because PyPy doesn't accept Path objects.
# TODO: This is a bug in PyPy and should be reported to them, but their
# GitLab instance was offline when we found this bug. :'(
ctx.load_cert_chain(
certfile=mkcert_certs.cert_file,
keyfile=mkcert_certs.key_file,
certfile=str(mkcert_certs.cert_file),
keyfile=str(mkcert_certs.key_file),
)

# we need keepalive_timeout=0
Expand Down

0 comments on commit 2f3c9ba

Please sign in to comment.