fizzpy 0.3.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fizzpy-0.5.0/.github/manylinux-deps.Dockerfile +15 -0
- fizzpy-0.5.0/.github/workflows/bump-fizz.yml +84 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/.github/workflows/check.yml +3 -3
- fizzpy-0.5.0/.github/workflows/wheels.yml +187 -0
- fizzpy-0.5.0/BUILDING.md +91 -0
- fizzpy-0.5.0/CHANGELOG.md +55 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/CMakeLists.txt +15 -7
- {fizzpy-0.3.0 → fizzpy-0.5.0}/Justfile +23 -0
- fizzpy-0.5.0/PKG-INFO +109 -0
- fizzpy-0.5.0/README.md +74 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/_core.cpp +434 -75
- fizzpy-0.5.0/docs/client.md +37 -0
- fizzpy-0.5.0/docs/limitations.md +16 -0
- fizzpy-0.5.0/docs/probe.md +89 -0
- fizzpy-0.5.0/docs/requests-httpx.md +94 -0
- fizzpy-0.5.0/docs/tls.md +113 -0
- fizzpy-0.5.0/examples/httpx_transport.py +47 -0
- fizzpy-0.5.0/examples/requests_adapter.py +45 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/fizzpy/__init__.py +23 -7
- fizzpy-0.5.0/fizzpy/__main__.py +235 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/fizzpy/_common.py +36 -4
- {fizzpy-0.3.0 → fizzpy-0.5.0}/fizzpy/_core.pyi +18 -0
- fizzpy-0.5.0/fizzpy/_tls.py +348 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/fizzpy/aio.py +8 -3
- fizzpy-0.5.0/fizzpy/contrib/__init__.py +8 -0
- fizzpy-0.5.0/fizzpy/contrib/_context.py +153 -0
- fizzpy-0.5.0/fizzpy/contrib/httpx.py +89 -0
- fizzpy-0.5.0/fizzpy/contrib/requests.py +85 -0
- fizzpy-0.5.0/fizzpy/probe.py +512 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/pyproject.toml +61 -15
- {fizzpy-0.3.0 → fizzpy-0.5.0}/scripts/build_fizz.py +5 -2
- {fizzpy-0.3.0 → fizzpy-0.5.0}/scripts/build_fizz_deps.py +5 -3
- fizzpy-0.5.0/scripts/build_fizz_deps_native.py +208 -0
- fizzpy-0.5.0/scripts/dev.py +226 -0
- fizzpy-0.5.0/scripts/fizz-tag.txt +1 -0
- fizzpy-0.5.0/scripts/repair_wheel_windows.py +47 -0
- fizzpy-0.5.0/scripts/wheel_smoke.py +42 -0
- fizzpy-0.5.0/tests/conftest.py +673 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/tests/test_basic.py +6 -0
- fizzpy-0.5.0/tests/test_cli.py +153 -0
- fizzpy-0.5.0/tests/test_concurrency.py +56 -0
- fizzpy-0.5.0/tests/test_contrib_httpx.py +93 -0
- fizzpy-0.5.0/tests/test_contrib_requests.py +92 -0
- fizzpy-0.5.0/tests/test_fallback.py +55 -0
- fizzpy-0.5.0/tests/test_free_threading.py +54 -0
- fizzpy-0.5.0/tests/test_probe.py +318 -0
- fizzpy-0.5.0/tests/test_proxy.py +43 -0
- fizzpy-0.5.0/tests/test_sni.py +44 -0
- fizzpy-0.5.0/tests/test_timeout.py +105 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/tests/test_verify.py +7 -3
- fizzpy-0.5.0/tests/test_wrap_socket.py +151 -0
- fizzpy-0.5.0/uv.lock +565 -0
- fizzpy-0.3.0/.github/manylinux-deps.Dockerfile +0 -16
- fizzpy-0.3.0/.github/workflows/wheels.yml +0 -118
- fizzpy-0.3.0/BUILDING.md +0 -57
- fizzpy-0.3.0/PKG-INFO +0 -163
- fizzpy-0.3.0/README.md +0 -136
- fizzpy-0.3.0/tests/conftest.py +0 -347
- fizzpy-0.3.0/uv.lock +0 -290
- {fizzpy-0.3.0 → fizzpy-0.5.0}/.github/workflows/publish.yml +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/.gitignore +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/.vscode/c_cpp_properties.json +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/.vscode/settings.json +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/LICENSE +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/docs/pq-handshake.png +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/examples/pq_marker.py +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/fizzpy/_http.py +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/fizzpy/_transport.py +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/pytest.ini +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/tests/_clienthello.py +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/tests/test_extensions.py +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/tests/test_http.py +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/tests/test_integration.py +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/tests/test_keepalive.py +0 -0
- {fizzpy-0.3.0 → fizzpy-0.5.0}/tests/test_redirects.py +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Manylinux base image with the folly + fizz + liboqs tree prebuilt, so wheel
|
|
2
|
+
# builds skip the ~40-min source build and just compile _core against it. Rebuilt
|
|
3
|
+
# by the deps_image job in .github/workflows/wheels.yml when build_fizz_deps.py or
|
|
4
|
+
# fizz-tag.txt changes. The dep prefixes land in $FIZZPY_BUILD_INFO/prefix.txt,
|
|
5
|
+
# which CMake reads at configure time (see CMakeLists.txt).
|
|
6
|
+
ARG MANYLINUX_IMAGE=quay.io/pypa/manylinux_2_28_x86_64:latest
|
|
7
|
+
FROM ${MANYLINUX_IMAGE}
|
|
8
|
+
|
|
9
|
+
ENV GETDEPS_SCRATCH=/opt/fizz-deps \
|
|
10
|
+
FIZZPY_BUILD_INFO=/opt/fizzpy-build \
|
|
11
|
+
FIZZ_SRC=/opt/fizz-src
|
|
12
|
+
|
|
13
|
+
# The script reads the pin from fizz-tag.txt beside itself.
|
|
14
|
+
COPY scripts/build_fizz_deps.py scripts/fizz-tag.txt /opt/
|
|
15
|
+
RUN python3 /opt/build_fizz_deps.py
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Bump Fizz
|
|
2
|
+
|
|
3
|
+
# Fizz tags weekly and the pin is the only thing keeping wheels current, so a
|
|
4
|
+
# monthly job opens a PR whenever a newer release exists. Pushes and PRs made with
|
|
5
|
+
# GITHUB_TOKEN don't trigger other workflows, but workflow_dispatch is exempt: the
|
|
6
|
+
# job dispatches the full wheel matrix and the lint check on the bump branch, and
|
|
7
|
+
# their results attach to the PR through the branch's head commit.
|
|
8
|
+
on:
|
|
9
|
+
schedule:
|
|
10
|
+
- cron: "0 6 1 * *"
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pull-requests: write
|
|
16
|
+
actions: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
bump:
|
|
20
|
+
name: Bump Fizz pin
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
timeout-minutes: 10
|
|
23
|
+
env:
|
|
24
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
25
|
+
BASE: ${{ github.event.repository.default_branch }}
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v7
|
|
28
|
+
|
|
29
|
+
- name: Find the newest Fizz release
|
|
30
|
+
id: latest
|
|
31
|
+
run: |
|
|
32
|
+
current=$(tr -d '[:space:]' < scripts/fizz-tag.txt)
|
|
33
|
+
latest=$(git ls-remote --tags --refs https://github.com/facebookincubator/fizz \
|
|
34
|
+
| sed 's|.*refs/tags/||' \
|
|
35
|
+
| grep -E '^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.[0-9]{2}$' \
|
|
36
|
+
| sort -V | tail -n1)
|
|
37
|
+
if [ -z "$latest" ]; then
|
|
38
|
+
echo "no release tags found" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
newest=$(printf '%s\n%s\n' "$current" "$latest" | sort -V | tail -n1)
|
|
42
|
+
if [ "$latest" = "$current" ] || [ "$newest" != "$latest" ]; then
|
|
43
|
+
echo "pin $current is already the newest release"
|
|
44
|
+
echo "bump=false" >> "$GITHUB_OUTPUT"
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
if git ls-remote --exit-code --heads origin "bump/fizz-$latest" > /dev/null; then
|
|
48
|
+
echo "bump/fizz-$latest already exists; leaving it alone"
|
|
49
|
+
echo "bump=false" >> "$GITHUB_OUTPUT"
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
{
|
|
53
|
+
echo "bump=true"
|
|
54
|
+
echo "current=$current"
|
|
55
|
+
echo "latest=$latest"
|
|
56
|
+
} >> "$GITHUB_OUTPUT"
|
|
57
|
+
|
|
58
|
+
- name: Open the bump PR
|
|
59
|
+
if: steps.latest.outputs.bump == 'true'
|
|
60
|
+
env:
|
|
61
|
+
CURRENT: ${{ steps.latest.outputs.current }}
|
|
62
|
+
LATEST: ${{ steps.latest.outputs.latest }}
|
|
63
|
+
run: |
|
|
64
|
+
branch="bump/fizz-$LATEST"
|
|
65
|
+
git config user.name "github-actions[bot]"
|
|
66
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
67
|
+
git switch -c "$branch"
|
|
68
|
+
echo "$LATEST" > scripts/fizz-tag.txt
|
|
69
|
+
git commit -am "build: bump Fizz to $LATEST"
|
|
70
|
+
git push origin "$branch"
|
|
71
|
+
|
|
72
|
+
gh pr create --base "$BASE" --head "$branch" \
|
|
73
|
+
--title "build: bump Fizz to $LATEST" \
|
|
74
|
+
--body "Bumps the Fizz pin from $CURRENT to $LATEST ([compare](https://github.com/facebookincubator/fizz/compare/$CURRENT...$LATEST)). The wheel matrix and lint check were dispatched on this branch; merge once they are green."
|
|
75
|
+
|
|
76
|
+
gh workflow run wheels.yml --ref "$branch"
|
|
77
|
+
gh workflow run check.yml --ref "$branch"
|
|
78
|
+
|
|
79
|
+
# Older bump PRs are superseded by this one.
|
|
80
|
+
gh pr list --state open --json number,headRefName \
|
|
81
|
+
--jq ".[] | select(.headRefName | startswith(\"bump/fizz-\")) | select(.headRefName != \"$branch\") | .number" \
|
|
82
|
+
| while read -r number; do
|
|
83
|
+
gh pr close "$number" --delete-branch --comment "Superseded by $LATEST."
|
|
84
|
+
done
|
|
@@ -9,7 +9,7 @@ on:
|
|
|
9
9
|
|
|
10
10
|
concurrency:
|
|
11
11
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
-
cancel-in-progress:
|
|
12
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
13
13
|
|
|
14
14
|
jobs:
|
|
15
15
|
quality:
|
|
@@ -17,10 +17,10 @@ jobs:
|
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
timeout-minutes: 10
|
|
19
19
|
steps:
|
|
20
|
-
- uses: actions/checkout@
|
|
20
|
+
- uses: actions/checkout@v7
|
|
21
21
|
|
|
22
22
|
- name: Install uv
|
|
23
|
-
uses: astral-sh/setup-uv@
|
|
23
|
+
uses: astral-sh/setup-uv@v10.3.3
|
|
24
24
|
with:
|
|
25
25
|
enable-cache: true
|
|
26
26
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
name: Wheels
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
workflow_call:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
# The folly+fizz+liboqs tree is baked into a GHCR image tagged by the hash of
|
|
17
|
+
# its build inputs. This job rebuilds it only when that hash has no image yet
|
|
18
|
+
# (deps changed, or first run); otherwise it's a fast existence check. Wheels
|
|
19
|
+
# depend on it, so they never run against a missing image.
|
|
20
|
+
deps_image:
|
|
21
|
+
name: Deps image
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 120
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
packages: write
|
|
27
|
+
outputs:
|
|
28
|
+
image: ${{ steps.meta.outputs.image }}
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v7
|
|
31
|
+
|
|
32
|
+
- name: Resolve image ref
|
|
33
|
+
id: meta
|
|
34
|
+
env:
|
|
35
|
+
OWNER: ${{ github.repository_owner }}
|
|
36
|
+
run: |
|
|
37
|
+
repo="ghcr.io/${OWNER,,}/fizz-py-deps"
|
|
38
|
+
tag="${{ hashFiles('scripts/build_fizz_deps.py', 'scripts/fizz-tag.txt', '.github/manylinux-deps.Dockerfile') }}"
|
|
39
|
+
echo "repo=$repo" >> "$GITHUB_OUTPUT"
|
|
40
|
+
echo "image=$repo:$tag" >> "$GITHUB_OUTPUT"
|
|
41
|
+
|
|
42
|
+
- name: Log in to GHCR
|
|
43
|
+
uses: docker/login-action@v4
|
|
44
|
+
with:
|
|
45
|
+
registry: ghcr.io
|
|
46
|
+
username: ${{ github.actor }}
|
|
47
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
48
|
+
|
|
49
|
+
- name: Check whether the image already exists
|
|
50
|
+
id: exists
|
|
51
|
+
run: |
|
|
52
|
+
if docker manifest inspect "${{ steps.meta.outputs.image }}" > /dev/null 2>&1; then
|
|
53
|
+
echo "found=true" >> "$GITHUB_OUTPUT"
|
|
54
|
+
else
|
|
55
|
+
echo "found=false" >> "$GITHUB_OUTPUT"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
- name: Build and push deps image
|
|
59
|
+
if: steps.exists.outputs.found == 'false'
|
|
60
|
+
uses: docker/build-push-action@v7
|
|
61
|
+
with:
|
|
62
|
+
context: .
|
|
63
|
+
file: .github/manylinux-deps.Dockerfile
|
|
64
|
+
push: true
|
|
65
|
+
tags: |
|
|
66
|
+
${{ steps.meta.outputs.image }}
|
|
67
|
+
${{ steps.meta.outputs.repo }}:latest
|
|
68
|
+
|
|
69
|
+
build_sdist:
|
|
70
|
+
name: Build sdist
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
timeout-minutes: 10
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v7
|
|
75
|
+
- run: pipx run build --sdist
|
|
76
|
+
- uses: actions/upload-artifact@v7
|
|
77
|
+
with:
|
|
78
|
+
name: sdist
|
|
79
|
+
path: dist/*.tar.gz
|
|
80
|
+
|
|
81
|
+
build_wheels:
|
|
82
|
+
# One job per interpreter, each compiling _core against the prebuilt deps
|
|
83
|
+
# image (minutes, not ~40 min). liboqs travels with fizz's getdeps manifest,
|
|
84
|
+
# giving real post-quantum (X25519MLKEM768) support.
|
|
85
|
+
name: Wheel ${{ matrix.python }}
|
|
86
|
+
needs: deps_image
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
timeout-minutes: 30
|
|
89
|
+
permissions:
|
|
90
|
+
contents: read
|
|
91
|
+
packages: read
|
|
92
|
+
strategy:
|
|
93
|
+
fail-fast: false
|
|
94
|
+
matrix:
|
|
95
|
+
python: [cp310, cp311, cp312, cp313, cp314, cp315, cp314t, cp315t]
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@v7
|
|
98
|
+
|
|
99
|
+
# cibuildwheel pulls the deps image via docker; authenticate so the GHCR
|
|
100
|
+
# package is reachable.
|
|
101
|
+
- name: Log in to GHCR
|
|
102
|
+
uses: docker/login-action@v4
|
|
103
|
+
with:
|
|
104
|
+
registry: ghcr.io
|
|
105
|
+
username: ${{ github.actor }}
|
|
106
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
107
|
+
|
|
108
|
+
- name: Build wheel
|
|
109
|
+
uses: pypa/cibuildwheel@v4.3.5
|
|
110
|
+
env:
|
|
111
|
+
CIBW_BUILD: ${{ matrix.python }}-*
|
|
112
|
+
CIBW_MANYLINUX_X86_64_IMAGE: ${{ needs.deps_image.outputs.image }}
|
|
113
|
+
|
|
114
|
+
- uses: actions/upload-artifact@v7
|
|
115
|
+
with:
|
|
116
|
+
name: wheels-${{ matrix.python }}
|
|
117
|
+
path: wheelhouse/*.whl
|
|
118
|
+
if-no-files-found: error
|
|
119
|
+
|
|
120
|
+
build_wheels_native:
|
|
121
|
+
# macOS and Windows have no prebuilt deps image, so each runner builds the
|
|
122
|
+
# folly+fizz+liboqs tree from source (cached). Skipped on PRs — PR feedback
|
|
123
|
+
# stays on the fast Linux path; these run on master, tags, and dispatch.
|
|
124
|
+
name: Wheel ${{ matrix.os }}
|
|
125
|
+
if: github.event_name != 'pull_request'
|
|
126
|
+
runs-on: ${{ matrix.os }}
|
|
127
|
+
timeout-minutes: 120
|
|
128
|
+
strategy:
|
|
129
|
+
fail-fast: false
|
|
130
|
+
matrix:
|
|
131
|
+
# macos-15-intel = x86_64, macos-15 = arm64 (native per runner). windows-2022
|
|
132
|
+
# ships VS 2022 (vc143); windows-latest moved to VS 2026, whose toolset Boost
|
|
133
|
+
# 1.83's bootstrap doesn't recognize. Interpreters (cp310-cp314) come from
|
|
134
|
+
# [tool.cibuildwheel] build in pyproject.toml.
|
|
135
|
+
os: [macos-15-intel, macos-15, windows-2022]
|
|
136
|
+
steps:
|
|
137
|
+
- uses: actions/checkout@v7
|
|
138
|
+
|
|
139
|
+
# before-all runs on the host on macOS/Windows (no container), so these are
|
|
140
|
+
# inherited by the deps build and the _core configure without CIBW_ENVIRONMENT.
|
|
141
|
+
- name: Resolve deps paths (unix)
|
|
142
|
+
if: runner.os != 'Windows'
|
|
143
|
+
shell: bash
|
|
144
|
+
run: |
|
|
145
|
+
{
|
|
146
|
+
echo "GETDEPS_SCRATCH=$HOME/fizz-deps"
|
|
147
|
+
echo "FIZZPY_BUILD_INFO=$HOME/fizzpy-build"
|
|
148
|
+
echo "FIZZ_SRC=$HOME/fizz-src"
|
|
149
|
+
# Match the runner's macOS major: getdeps pulls Homebrew libs (openssl,
|
|
150
|
+
# sodium, …) built for this OS, so the wheel can't claim an older target
|
|
151
|
+
# than they carry. Also satisfies folly's C++17 (>= 10.13) requirement.
|
|
152
|
+
echo "MACOSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion | cut -d. -f1).0"
|
|
153
|
+
} >> "$GITHUB_ENV"
|
|
154
|
+
|
|
155
|
+
- name: Resolve deps paths and locate MSVC (windows)
|
|
156
|
+
if: runner.os == 'Windows'
|
|
157
|
+
shell: bash
|
|
158
|
+
run: |
|
|
159
|
+
vswhere="/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
|
|
160
|
+
install=$("$vswhere" -latest -property installationPath)
|
|
161
|
+
{
|
|
162
|
+
echo "GETDEPS_SCRATCH=C:/fizz-deps"
|
|
163
|
+
echo "FIZZPY_BUILD_INFO=C:/fizzpy-build"
|
|
164
|
+
echo "FIZZ_SRC=C:/fizz-src"
|
|
165
|
+
# getdeps only globs for VS 2022; point it at whatever VS is installed.
|
|
166
|
+
echo "VCVARS_PATH=$install\\VC\\Auxiliary\\Build\\vcvarsall.bat"
|
|
167
|
+
} >> "$GITHUB_ENV"
|
|
168
|
+
|
|
169
|
+
# The getdeps tree is the ~40-min cost; key it on the build script + the Fizz
|
|
170
|
+
# pin (scripts/fizz-tag.txt) so a warm cache turns before-all into a near no-op.
|
|
171
|
+
- name: Cache deps tree
|
|
172
|
+
uses: actions/cache@v6
|
|
173
|
+
with:
|
|
174
|
+
path: |
|
|
175
|
+
${{ env.GETDEPS_SCRATCH }}
|
|
176
|
+
${{ env.FIZZPY_BUILD_INFO }}
|
|
177
|
+
${{ env.FIZZ_SRC }}
|
|
178
|
+
key: deps-${{ matrix.os }}-${{ env.MACOSX_DEPLOYMENT_TARGET }}-${{ hashFiles('scripts/fizz-tag.txt', 'scripts/build_fizz_deps_native.py') }}
|
|
179
|
+
|
|
180
|
+
- name: Build wheels
|
|
181
|
+
uses: pypa/cibuildwheel@v4.3.5
|
|
182
|
+
|
|
183
|
+
- uses: actions/upload-artifact@v7
|
|
184
|
+
with:
|
|
185
|
+
name: wheels-${{ matrix.os }}
|
|
186
|
+
path: wheelhouse/*.whl
|
|
187
|
+
if-no-files-found: error
|
fizzpy-0.5.0/BUILDING.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Building fizzpy
|
|
2
|
+
|
|
3
|
+
The `_core` extension links Facebook's Fizz (C++ TLS 1.3). The post-quantum
|
|
4
|
+
handshake additionally requires a Fizz built against
|
|
5
|
+
[liboqs](https://github.com/open-quantum-safe/liboqs) — the Homebrew bottle and
|
|
6
|
+
vcpkg port both ship `FIZZ_HAVE_OQS=0`, so they negotiate classical groups only.
|
|
7
|
+
|
|
8
|
+
There are three build paths, for three different purposes:
|
|
9
|
+
|
|
10
|
+
| | Container dev (`just dev-build`) | Homebrew dev (`just build`) | CI wheels (`scripts/build_fizz_deps.py`) |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| Fizz/liboqs source | getdeps tree in `.dev/getdeps` | Homebrew + from-source Fizz | getdeps, all from source |
|
|
13
|
+
| Linkage | static, against the CI tree | static Fizz, shared liboqs via rpath | fully static-PIC, self-contained `.so` |
|
|
14
|
+
| Where | `manylinux_2_28` container | your machine | `manylinux_2_28` container |
|
|
15
|
+
| Optimized for | "builds here == builds in CI" | fastest iteration | deterministic, portable distribution |
|
|
16
|
+
|
|
17
|
+
## Container development (`just dev-*`) — recommended
|
|
18
|
+
|
|
19
|
+
The Homebrew loop links `_core` against Homebrew's *shared* folly, so a Homebrew
|
|
20
|
+
folly bump (now compiled for a newer glibc than many hosts) silently breaks every
|
|
21
|
+
local build. The container loop sidesteps that: it builds and runs `_core` inside
|
|
22
|
+
the same `manylinux_2_28` image the wheels use, against the prebuilt static
|
|
23
|
+
folly + Fizz + liboqs tree in `.dev/getdeps`. "Builds locally" and "builds in CI"
|
|
24
|
+
become the same statement.
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
just dev-up # create/start the container + one-time system + pip setup
|
|
28
|
+
just dev-build # editable-build _core against the getdeps tree (~10s inner loop)
|
|
29
|
+
just dev-test # offline suite; `just dev-test "-- -m network"` runs live-network tests
|
|
30
|
+
just dev-shell # interactive bash inside the container
|
|
31
|
+
just dev-down # stop + remove the container
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The trick that needs no path rewriting: the getdeps tree bakes absolute
|
|
35
|
+
`/project/...` paths into its CMake configs (it was built at `/project`), so the
|
|
36
|
+
repo is bind-mounted at `/project` inside the container and every baked path
|
|
37
|
+
resolves. A persistent container plus `docker exec` keeps the inner loop fast
|
|
38
|
+
(no per-change `docker run`/image build).
|
|
39
|
+
|
|
40
|
+
This requires the `.dev/getdeps` tree on disk (gitignored). Rebuild it inside the
|
|
41
|
+
image with `scripts/build_fizz_deps.py` if it's missing (~40 min, one-time). The
|
|
42
|
+
orchestration lives in `scripts/dev.py`.
|
|
43
|
+
|
|
44
|
+
## Homebrew development (`just build`) — fastest, host-dependent
|
|
45
|
+
|
|
46
|
+
Fast iteration when your host glibc is new enough for the current Homebrew folly
|
|
47
|
+
bottle. If `import fizzpy` fails with a `GLIBC_2.xx not found` error, your host is
|
|
48
|
+
too old — use the container loop above.
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
brew install fizz liboqs # fizz pulls folly, boost, glog, gflags, libsodium, double-conversion
|
|
52
|
+
pip install -e .[dev] --no-build-isolation # or: pip install scikit-build-core pybind11 pybind11-stubgen ruff pytest
|
|
53
|
+
|
|
54
|
+
just build # build OQS Fizz if needed (cached), then editable-install _core
|
|
55
|
+
just test # offline test suite; `just test ""` includes live network tests
|
|
56
|
+
just check # ruff lint
|
|
57
|
+
just fmt # ruff format + clang-format _core.cpp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`just build` runs `scripts/build_fizz.py ensure`, which compiles a liboqs-enabled
|
|
61
|
+
Fizz into the gitignored `.dev/fizz-prefix` once (subsequent runs are a no-op;
|
|
62
|
+
`python scripts/build_fizz.py ensure --force` rebuilds), then editable-installs
|
|
63
|
+
`_core` with `find_package(fizz)` pointed at that prefix.
|
|
64
|
+
|
|
65
|
+
- **C++20 is required** — current Folly (2026.06) dropped C++17.
|
|
66
|
+
- Fizz and Folly are versioned together. The Fizz tag lives in
|
|
67
|
+
`scripts/fizz-tag.txt`, shared by every build path; it must match the Homebrew
|
|
68
|
+
Folly bottle here, or the ABI won't match (`FIZZ_TAG=... just build` overrides
|
|
69
|
+
it when brew lags). A monthly workflow (`bump-fizz.yml`) opens a PR when a newer
|
|
70
|
+
Fizz release exists.
|
|
71
|
+
- `find_package(liboqs CONFIG)` inside Fizz auto-sets `FIZZ_HAVE_OQS` and links
|
|
72
|
+
`OQS::oqs`. `BUILD_EXAMPLES=OFF` is required: the bundled FizzTool fails to
|
|
73
|
+
compile under OpenSSL 3.6 (`-Werror` on the deprecated `EVP_PKEY_cmp`); the
|
|
74
|
+
library itself is fine.
|
|
75
|
+
|
|
76
|
+
## Portable wheels (CI)
|
|
77
|
+
|
|
78
|
+
`scripts/build_fizz_deps.py` is cibuildwheel's `before-all`. It builds the whole
|
|
79
|
+
folly + Fizz + liboqs tree once per `manylinux_2_28` container as static-PIC
|
|
80
|
+
archives, which `_core` links in wholesale — a single self-contained extension
|
|
81
|
+
with exactly one copy of folly. (Using getdeps `--shared-lib` instead would bake
|
|
82
|
+
folly into `libfizz.so` *and* drag it into `_core` again, double-registering
|
|
83
|
+
folly's gflags globals and aborting at import.) The build settings live in
|
|
84
|
+
`[tool.cibuildwheel]` in `pyproject.toml`.
|
|
85
|
+
|
|
86
|
+
## Verify
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
python -c "import fizzpy; print(fizzpy.get('https://www.cloudflare.com').tls)"
|
|
90
|
+
# group should be 'X25519MLKEM768', group_code 4588
|
|
91
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.5.0] - 2026-11-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `fizzpy.wrap_socket(sock, server_hostname, config)` performs a Fizz TLS 1.3 handshake over an already-connected socket and returns a blocking, `ssl.SSLSocket`-shaped `TlsSocket`. `TlsConfig` holds the handshake options (verification, CA file, key groups, ALPN, extensions, timeout). An empty server name sends no SNI extension.
|
|
13
|
+
- `fizzpy.contrib.requests.FizzAdapter` mounts Fizz under `requests` and urllib3. Install it with `pip install fizzpy[requests]`.
|
|
14
|
+
- `fizzpy.contrib.httpx.FizzHTTPTransport` does the same for synchronous `httpx`. Install it with `pip install fizzpy[httpx]`. It rejects `verify=`, `cert=` and `http2=True` instead of ignoring them.
|
|
15
|
+
- Opt-in TLS 1.2 fallback: `FizzAdapter(fallback=True)` and `FizzHTTPTransport(fallback=True)` retry over the standard library `ssl` module when the server rejects TLS 1.3 with a `protocol_version` alert. The retry is a classical, non post-quantum handshake. Certificate and hostname failures are never retried.
|
|
16
|
+
- Plain `CONNECT` proxies work with the adapters. The HTTP client opens the tunnel and fizzpy handshakes over it. Proxies that themselves require TLS are not supported.
|
|
17
|
+
- `fizzpy probe`, a command line check for which hosts complete a post-quantum handshake. It takes hosts or a file, can print JSON, and `--require-pq` exits 1 unless every target is post-quantum. `fizzpy.probe.probe` and `probe_many` are the Python API. See `docs/probe.md`.
|
|
18
|
+
- `fizzpy.supports_post_quantum()` reports whether the installed build can do the hybrid key exchange. It is always true for the wheels. A source build against a Fizz without liboqs returns false and now defaults to classical x25519 only, where it previously could not generate the hybrid key share.
|
|
19
|
+
- TLS handshake errors carry a `fizz_alert` attribute with the alert name (for example `protocol_version`) and a `fizz_alert_remote` attribute saying whether the peer sent it.
|
|
20
|
+
- Wheels for CPython 3.15 and for free-threaded CPython 3.14t and 3.15t, on every platform that has wheels.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Callers must update exception handling. Handshake failures from `Client`, `AsyncClient` and the new APIs now raise `ssl.SSLError`, or `ssl.SSLCertVerificationError` for certificate and hostname failures, instead of `ConnectionError`. Read and write timeouts raise `socket.timeout` instead of `ConnectionError`. Both are `OSError` subclasses, so `except OSError` still catches them, but `except ConnectionError` no longer does. Connection setup failures such as a refused connection still raise `ConnectionError`.
|
|
25
|
+
- The `timeout` option now bounds each read and write as well as the handshake, so a stalled peer raises `socket.timeout` instead of hanging. `timeout=None` means no deadline, and a zero or negative timeout fails fast rather than blocking forever.
|
|
26
|
+
- The wheels now bundle Fizz v2026.10.26.00 (previously v2026.06.08.00).
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- The Windows wheels no longer risk crashing the process at interpreter exit. The shared event loop thread is now left running instead of being joined while the extension unloads.
|
|
31
|
+
|
|
32
|
+
## [0.4.0] - 2026-06-16
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Wheels for macOS (arm64 and x86_64) and Windows (x64), alongside the existing Linux x86_64 wheels. Like the Linux wheels, they are self-contained and include post-quantum support.
|
|
37
|
+
|
|
38
|
+
## [0.3.0] - 2026-06-13
|
|
39
|
+
|
|
40
|
+
Initial release, supporting TLS 1.3 and HTTP/1.1 only.
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- TLS 1.3 HTTP client built on Facebook's Fizz: synchronous `fizzpy.Client` with module-level `get`, `post`, `head`, `put`, `delete` and `request`, and an asyncio `fizzpy.aio.AsyncClient` over the same native core.
|
|
45
|
+
- Post-quantum key exchange by default: the client offers the hybrid `X25519MLKEM768` group and classical `x25519`, and falls back to x25519 against servers without ML-KEM. Use `groups=[NamedGroup.x25519_mlkem768]` to require post-quantum or `groups=[NamedGroup.x25519]` to disable it.
|
|
46
|
+
- Every response carries the negotiated TLS parameters in `Response.tls` (version, cipher, group, ALPN, SNI, peer certificate).
|
|
47
|
+
- Certificate chain and hostname verification against the `certifi` bundle by default, with `cafile=` for a custom CA and `verify=False` to turn it off.
|
|
48
|
+
- HTTP/1.1 responses with content-length, chunked and gzip/deflate bodies, per-host keep-alive connection pooling, and automatic redirect following with `follow_redirects` and `max_redirects` (`TooManyRedirects` when exceeded).
|
|
49
|
+
- `Extension(type, data)` to append custom extensions to the ClientHello, through `extensions=`.
|
|
50
|
+
- Type stubs for the native `fizzpy._core` module.
|
|
51
|
+
- Self-contained wheels for Linux x86_64 (`manylinux_2_28`), CPython 3.10 to 3.14. Fizz, folly and liboqs are linked in, so no system packages are needed.
|
|
52
|
+
|
|
53
|
+
[0.5.0]: https://github.com/Xevion/fizz-py/compare/v0.4.0...v0.5.0
|
|
54
|
+
[0.4.0]: https://github.com/Xevion/fizz-py/compare/v0.3.0...v0.4.0
|
|
55
|
+
[0.3.0]: https://github.com/Xevion/fizz-py/releases/tag/v0.3.0
|
|
@@ -7,15 +7,15 @@ project(
|
|
|
7
7
|
VERSION ${SKBUILD_PROJECT_VERSION}
|
|
8
8
|
LANGUAGES CXX)
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
# fizz + liboqs source build
|
|
12
|
-
|
|
13
|
-
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|
10
|
+
# Wheels target Linux, macOS, and Windows. Other platforms stop here before the
|
|
11
|
+
# (~40 min) folly + fizz + liboqs source build an sdist install would attempt.
|
|
12
|
+
if(NOT CMAKE_SYSTEM_NAME MATCHES "^(Linux|Darwin|Windows)$")
|
|
14
13
|
message(
|
|
15
14
|
FATAL_ERROR
|
|
16
|
-
"fizzpy publishes prebuilt Linux
|
|
17
|
-
"is not
|
|
18
|
-
"BUILDING.md
|
|
15
|
+
"fizzpy publishes prebuilt wheels for Linux, macOS, and Windows; "
|
|
16
|
+
"${CMAKE_SYSTEM_NAME} is not supported. Building from source needs the Fizz "
|
|
17
|
+
"toolchain — see BUILDING.md "
|
|
18
|
+
"(https://github.com/Xevion/fizz-py/blob/master/BUILDING.md).")
|
|
19
19
|
endif()
|
|
20
20
|
|
|
21
21
|
# Fizz's CMake config pulls in its bundled FindSodium.cmake. Homebrew installs
|
|
@@ -85,4 +85,12 @@ if(TARGET Folly::folly_io_async_scoped_event_base_thread)
|
|
|
85
85
|
target_link_libraries(_core PRIVATE Folly::folly_io_async_scoped_event_base_thread)
|
|
86
86
|
endif()
|
|
87
87
|
|
|
88
|
+
# Static deps need Windows system libs their getdeps configs don't propagate as
|
|
89
|
+
# interface links: bcrypt (RNG: libevent, liboqs), crypt32 (OpenSSL cert store),
|
|
90
|
+
# ws2_32 (sockets), iphlpapi (folly GetAdaptersAddresses), advapi32 (OpenSSL/
|
|
91
|
+
# libevent registry + RNG fallback). Link them on the consumer.
|
|
92
|
+
if(WIN32)
|
|
93
|
+
target_link_libraries(_core PRIVATE bcrypt crypt32 ws2_32 iphlpapi advapi32)
|
|
94
|
+
endif()
|
|
95
|
+
|
|
88
96
|
install(TARGETS _core LIBRARY DESTINATION fizzpy)
|
|
@@ -19,6 +19,29 @@ wheel:
|
|
|
19
19
|
test *args="-m 'not network'":
|
|
20
20
|
pytest {{args}}
|
|
21
21
|
|
|
22
|
+
# Containerized dev loop (recommended): build + test inside the manylinux image
|
|
23
|
+
# the wheels use, against the prebuilt .dev/getdeps tree. Isolated from host
|
|
24
|
+
# Homebrew/glibc drift. See scripts/dev.py.
|
|
25
|
+
dev-up:
|
|
26
|
+
python scripts/dev.py up
|
|
27
|
+
|
|
28
|
+
# Editable-build _core in the container (the ~10s inner loop).
|
|
29
|
+
dev-build:
|
|
30
|
+
python scripts/dev.py build
|
|
31
|
+
|
|
32
|
+
# Run the suite in the container. `just dev-test "-- -m network"` runs the
|
|
33
|
+
# live-network tests; pass any other pytest args after a `--` the same way.
|
|
34
|
+
dev-test *args:
|
|
35
|
+
python scripts/dev.py test {{args}}
|
|
36
|
+
|
|
37
|
+
# Interactive shell inside the dev container.
|
|
38
|
+
dev-shell:
|
|
39
|
+
python scripts/dev.py shell
|
|
40
|
+
|
|
41
|
+
# Stop + remove the dev container.
|
|
42
|
+
dev-down:
|
|
43
|
+
python scripts/dev.py down
|
|
44
|
+
|
|
22
45
|
# Install the lint/type toolchain into .venv without building _core.
|
|
23
46
|
_tools:
|
|
24
47
|
uv sync --no-install-project --group dev
|
fizzpy-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fizzpy
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Post-quantum TLS 1.3 for Python — plug it under requests/urllib3 or use the built-in client. Built on Facebook's Fizz.
|
|
5
|
+
Author-Email: Xevion <xevion@xevion.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Operating System :: MacOS
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
20
|
+
Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Security :: Cryptography
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Project-URL: Repository, https://github.com/Xevion/fizz-py
|
|
25
|
+
Project-URL: Issues, https://github.com/Xevion/fizz-py/issues
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: certifi
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
30
|
+
Provides-Extra: requests
|
|
31
|
+
Requires-Dist: requests; extra == "requests"
|
|
32
|
+
Provides-Extra: httpx
|
|
33
|
+
Requires-Dist: httpx; extra == "httpx"
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# fizzpy
|
|
37
|
+
|
|
38
|
+
Post-quantum TLS 1.3 for Python. Keep the HTTP client you already use and swap only
|
|
39
|
+
the TLS: fizzpy performs the handshake with the hybrid `X25519MLKEM768` key exchange,
|
|
40
|
+
built on Facebook's [Fizz](https://github.com/facebookincubator/fizz) (C++).
|
|
41
|
+
|
|
42
|
+
**Why.** The key exchange is the part of TLS exposed to "harvest now, decrypt later"
|
|
43
|
+
recording, which is why browsers moved to hybrid ML-KEM. In Python, ML-KEM arrived with
|
|
44
|
+
OpenSSL 3.5, so an interpreter linked against anything older cannot negotiate it.
|
|
45
|
+
fizzpy carries its own TLS stack (Fizz and liboqs), so it works there, and it can
|
|
46
|
+
check whether the servers you run or depend on support post-quantum key exchange.
|
|
47
|
+
Beta: the API may change before 1.0.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import requests
|
|
51
|
+
from fizzpy.contrib.requests import FizzAdapter
|
|
52
|
+
|
|
53
|
+
session = requests.Session()
|
|
54
|
+
session.mount("https://", FizzAdapter())
|
|
55
|
+
print(session.get("https://www.cloudflare.com").status_code)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
requests still owns cookies, redirects, retries and pooling; fizzpy only does the
|
|
59
|
+
handshake. The same works under [httpx](docs/requests-httpx.md#httpx), on any
|
|
60
|
+
[connected socket](docs/tls.md#any-socket-wrap_socket), or through the small
|
|
61
|
+
[built-in client](docs/client.md).
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
*A fizzpy ClientHello in Wireshark: it offers `X25519MLKEM768` (`0x11ec`) plus a
|
|
66
|
+
custom marker extension from [`examples/pq_marker.py`](examples/pq_marker.py).*
|
|
67
|
+
|
|
68
|
+
## Check a server
|
|
69
|
+
|
|
70
|
+
```console
|
|
71
|
+
$ fizzpy probe www.cloudflare.com github.com
|
|
72
|
+
HOST VERDICT PREFERRED ML-KEM x25519 P-256 P-384
|
|
73
|
+
www.cloudflare.com post-quantum X25519MLKEM768 ok ok ok ok
|
|
74
|
+
github.com classical-only x25519 rej ok ok rej
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
One handshake per key-exchange group tells "supports ML-KEM" apart from "prefers
|
|
78
|
+
x25519", and finds hosts where the larger post-quantum hello gets a connection reset.
|
|
79
|
+
It scales to thousands of hosts and prints JSON. See [the probe guide](docs/probe.md).
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
pip install fizzpy
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Wheels are self-contained (Fizz and liboqs are built in) for Linux x86_64, macOS and
|
|
88
|
+
Windows x64, CPython 3.10 to 3.15 including free-threaded builds. For anything else,
|
|
89
|
+
see [BUILDING.md](BUILDING.md).
|
|
90
|
+
|
|
91
|
+
## Limits
|
|
92
|
+
|
|
93
|
+
- TLS 1.3 only, so TLS 1.2-only servers fail unless you opt into
|
|
94
|
+
[a classical fallback](docs/tls.md#reaching-tls-12-only-hosts).
|
|
95
|
+
- HTTP/1.1 only.
|
|
96
|
+
- No client certificates yet.
|
|
97
|
+
|
|
98
|
+
More in [docs/limitations.md](docs/limitations.md).
|
|
99
|
+
|
|
100
|
+
## Docs
|
|
101
|
+
|
|
102
|
+
- [Under requests and httpx](docs/requests-httpx.md)
|
|
103
|
+
- [Handshake control](docs/tls.md): `wrap_socket`, `TlsConfig`, key exchange,
|
|
104
|
+
extensions, certificates
|
|
105
|
+
- [The built-in client](docs/client.md)
|
|
106
|
+
- [The probe](docs/probe.md)
|
|
107
|
+
- [Building](BUILDING.md)
|
|
108
|
+
|
|
109
|
+
MIT licensed.
|