Skip to content

Commit 571749b

Browse files
committed
Atopt ua-parser-rs from uap-rust
Originally, building ua-parser-rs alongside uap-rust seemed to make sense, but over time I've soured on it: although it's largely Rust code: - The interface for python and (should) follow Python API design principles. - It's bound much more to ua-parser/uap-python (as an implementation detail thereof) than to ua-parser/uap-rust (which is just a dependency). - The tests are pure Python. - The CI and release pipeline are completely pipeline coded (testing and releasing for various python runtimes). And in case the Python and Rust teams split in the future, it probably doesn't make sense for the python team to have to go through the rust team to tune the extension, even if they may want / need counsel from that team.
1 parent e44871c commit 571749b

13 files changed

Lines changed: 1123 additions & 0 deletions

File tree

.github/workflows/py-checks.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: py checks
2+
3+
on:
4+
pull_request:
5+
push:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
py-checks:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout working copy
15+
uses: actions/checkout@v4
16+
with:
17+
persist-credentials: false
18+
- name: ruff check
19+
uses: chartboost/ruff-action@v1
20+
with:
21+
args: check
22+
- name: ruff format
23+
if: always()
24+
uses: chartboost/ruff-action@v1
25+
with:
26+
args: format --diff
27+
- name: Set up Python
28+
id: setup_python
29+
if: always()
30+
uses: actions/setup-python@v5
31+
with:
32+
python-version: "3.x"
33+
- name: Install mypy
34+
id: install_mypy
35+
if: ${{ always() && steps.setup_python.conclusion == 'success' }}
36+
run: |
37+
python -mpip install --upgrade pip
38+
python -mpip install mypy pytest types-PyYaml
39+
- name: mypy
40+
if: ${{ always() && steps.install_mypy.conclusion == 'success' }}
41+
run: mypy --strict .

.github/workflows/py-tests.yml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
py-test-wheels:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
python-version:
16+
- "3.x"
17+
- "3.14t"
18+
- "pypy-3.11"
19+
- "graalpy-24"
20+
- "graalpy-25"
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
with:
25+
persist-credentials: false
26+
- uses: actions/setup-python@v6
27+
with:
28+
python-version: ${{ matrix.python-version }}
29+
30+
- uses: actions/cache@v4
31+
with:
32+
path: |
33+
~/.cargo/bin/
34+
~/.cargo/registry/index/
35+
~/.cargo/registry/cache/
36+
~/.cargo/git/db/
37+
target/
38+
key: ${{ runner.os }}-cargo-${{ hashFiles('ua-parser-rs/Cargo.toml') }}
39+
restore-keys: |
40+
${{ runner.os }}-cargo-
41+
42+
- uses: actions/cache@v4
43+
with:
44+
path: |
45+
~/.cache/pip
46+
~/.cache/pip-graalpy
47+
key: ${{ runner.os }}-pip-maturin-${{ matrix.python-version }}
48+
49+
- uses: PyO3/maturin-action@v1
50+
with:
51+
args: --release --out dist -m ua-parser-rs/Cargo.toml -i python --zig
52+
sccache: true
53+
- uses: actions/upload-artifact@v4
54+
with:
55+
name: wheels-${{ matrix.python-version }}
56+
path: dist/*
57+
retention-days: 1
58+
compression-level: 0
59+
60+
py-tests:
61+
needs: py-test-wheels
62+
runs-on: ubuntu-latest
63+
strategy:
64+
fail-fast: false
65+
matrix:
66+
python-version:
67+
- "3.10"
68+
- "3.11"
69+
- "3.12"
70+
- "3.13"
71+
- "3.14t"
72+
- "pypy-3.11"
73+
- "graalpy-24"
74+
- "graalpy-25"
75+
76+
include:
77+
- wheel: "3.x"
78+
- python-version: "3.14t"
79+
wheel: "3.14t"
80+
- python-version: "pypy-3.11"
81+
wheel: "pypy-3.11"
82+
- python-version: "graalpy-24"
83+
wheel: "graalpy-24"
84+
- python-version: "graalpy-25"
85+
wheel: "graalpy-25"
86+
87+
steps:
88+
- uses: actions/checkout@v4
89+
with:
90+
submodules: true
91+
persist-credentials: false
92+
- uses: actions/setup-python@v6
93+
with:
94+
python-version: ${{ matrix.python-version }}
95+
allow-prereleases: true
96+
- uses: actions/cache@v4
97+
with:
98+
path: |
99+
~/.cache/pip
100+
~/.cache/pip-graalpy
101+
key: ${{ runner.os }}-pip-${{ matrix.python-version }}
102+
- uses: actions/download-artifact@v4
103+
with:
104+
name: wheels-${{ matrix.wheel }}
105+
path: dist
106+
- run: python -mpip install --upgrade pip
107+
- run: |
108+
if ! pip download --only-binary :all: pyyaml > /dev/null 2>&1
109+
then
110+
sudo apt install libyaml-dev
111+
fi
112+
- run: python -mpip install pytest pyyaml
113+
- run: python -mpip install --find-links dist ua_parser_rs
114+
- run: pytest -v -Werror -ra ua-parser-rs
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
name: Wheels
2+
3+
on:
4+
#pull_request:
5+
workflow_dispatch:
6+
inputs:
7+
release:
8+
description: 'Push wheels to pypi'
9+
type: boolean
10+
default: false
11+
required: true
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
py-wheels-matrix:
18+
name: "generate build matrix"
19+
runs-on: ubuntu-latest
20+
outputs:
21+
matrix: ${{ steps.make-matrix.outputs.matrix }}
22+
steps:
23+
- id: make-matrix
24+
shell: python
25+
name: generate matrix
26+
run: |
27+
import itertools
28+
import json
29+
import os
30+
import pprint
31+
32+
builder = {
33+
('linux', 'x86_64'): 'ubuntu-latest',
34+
('linux', 'aarch64'): 'ubuntu-24.04-arm',
35+
('musllinux', 'x86_64'): 'ubuntu-latest',
36+
('musllinux', 'aarch64'): 'ubuntu-24.04-arm',
37+
('macos', 'x86_64'): 'macos-15-intel',
38+
('macos', 'aarch64'): 'macos-latest',
39+
('windows', 'x86_64'): 'windows-latest',
40+
('windows', 'aarch64'): 'windows-11-arm',
41+
}
42+
43+
matrix = [
44+
d
45+
for d in map(dict, itertools.product(
46+
(('python-version', v) for v in ["3.x", "3.14t", "pypy-3.11", "graalpy-24", "graalpy-25"]),
47+
(('arch', a) for a in ["x86_64", "aarch64"]),
48+
(('platform', p) for p in ["linux", "musllinux", "windows", "macos"])
49+
))
50+
# on windows, only cpython has arm builds (?)
51+
if d['python-version'].startswith('3.') \
52+
or d['platform'] != 'windows' \
53+
or d['arch'] != 'aarch64'
54+
]
55+
for job in matrix:
56+
match job['platform']:
57+
case 'linux':
58+
job['manylinux'] = 'auto'
59+
job['args'] = ' --zig'
60+
case 'mussllinux':
61+
job['manylinux'] = 'musllinux_1_2'
62+
63+
job['runs'] = builder[job['platform'], job['arch']]
64+
65+
with open(os.environ['GITHUB_OUTPUT'], 'w') as f:
66+
f.write("matrix=")
67+
json.dump({'include': matrix}, f)
68+
f.flush()
69+
70+
py-release-wheels:
71+
needs: [py-wheels-matrix]
72+
strategy:
73+
fail-fast: false
74+
matrix: ${{fromJson(needs.py-wheels-matrix.outputs.matrix)}}
75+
76+
runs-on: ${{ matrix.runs }}
77+
78+
steps:
79+
- uses: actions/checkout@v4
80+
with:
81+
persist-credentials: false
82+
- uses: actions/setup-python@v6
83+
with:
84+
python-version: ${{ matrix.python-version }}
85+
# windows/arm doesn't have a rust toolchain by default
86+
- if: matrix.platform == 'windows' && matrix.arch == 'aarch64'
87+
uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 # 1.12.0
88+
- name: Build wheels
89+
uses: PyO3/maturin-action@v1
90+
with:
91+
args: --release --out dist -m ua-parser-rs/Cargo.toml -i python ${{ matrix.args }}
92+
sccache: 'true'
93+
manylinux: ${{ matrix.manylinux }}
94+
- name: Upload wheels
95+
uses: actions/upload-artifact@v4
96+
with:
97+
name: wheels-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.python-version }}
98+
path: dist/*
99+
retention-days: 1
100+
compression-level: 0
101+
102+
py-release-sdist:
103+
runs-on: ubuntu-latest
104+
steps:
105+
- uses: actions/checkout@v4
106+
with:
107+
persist-credentials: false
108+
- name: Build sdist
109+
uses: PyO3/maturin-action@v1
110+
with:
111+
command: sdist
112+
args: --out dist -m ua-parser-rs/Cargo.toml
113+
- name: Upload sdist
114+
uses: actions/upload-artifact@v4
115+
with:
116+
name: wheels-sdist
117+
path: dist
118+
119+
py-release-tests:
120+
needs: py-release-wheels
121+
122+
strategy:
123+
fail-fast: false
124+
matrix:
125+
python-version:
126+
- "3.10"
127+
- "3.11"
128+
- "3.12"
129+
- "3.13"
130+
- "3.14"
131+
- "3.14t"
132+
- "pypy-3.11"
133+
- "graalpy-24"
134+
- "graalpy-25"
135+
platform:
136+
- linux
137+
# probably requires a custom image of some sort
138+
# - musllinux
139+
- windows
140+
- macos
141+
arch:
142+
- x86_64
143+
- aarch64
144+
145+
exclude:
146+
- platform: windows
147+
python-version: 3.10
148+
arch: aarch64
149+
- platform: windows
150+
arch: aarch64
151+
python-version: pypy-3.11
152+
- platform: windows
153+
python-version: graalpy-24
154+
- platform: windows
155+
python-version: graalpy-25
156+
157+
include:
158+
- wheel: "3.x"
159+
- python-version: "3.14t"
160+
wheel: "3.14t"
161+
- python-version: "pypy-3.11"
162+
wheel: "pypy-3.11"
163+
- python-version: "graalpy-24"
164+
wheel: "graalpy-24"
165+
- python-version: "graalpy-25"
166+
wheel: "graalpy-25"
167+
168+
- runner: ubuntu-latest
169+
- arch: aarch64
170+
runner: ubuntu-24.04-arm
171+
- platform: windows
172+
runner: windows-latest
173+
- platform: windows
174+
arch: aarch64
175+
runner: windows-11-arm
176+
- platform: macos
177+
runner: macos-latest
178+
- platform: macos
179+
arch: x86_64
180+
runner: macos-15-intel
181+
182+
runs-on: ${{ matrix.runner }}
183+
184+
steps:
185+
- name: Checkout working copy
186+
uses: actions/checkout@v4
187+
with:
188+
submodules: true
189+
persist-credentials: false
190+
- name: Set up Python ${{ matrix.python-version }}
191+
uses: actions/setup-python@v6
192+
with:
193+
python-version: ${{ matrix.python-version }}
194+
allow-prereleases: true
195+
- name: Retrieve wheel
196+
uses: actions/download-artifact@v4
197+
with:
198+
name: wheels-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.wheel }}
199+
path: dist
200+
- name: Update pip
201+
run: python -mpip install --upgrade pip
202+
- name: Maybe install libyaml-dev
203+
if: startsWith(matrix.runs, 'ubuntu-latest')
204+
run: |
205+
# if binary wheels are not available for the current
206+
# package install libyaml-dev so we can install pyyaml
207+
# from source
208+
if ! pip download --only-binary :all: pyyaml > /dev/null 2>&1; then
209+
sudo apt install libyaml-dev
210+
fi
211+
- name: Install test dependencies
212+
run: python -mpip install pytest pyyaml
213+
- name: Install wheel
214+
run: python -mpip install --only-binary ':all:' --no-index --find-links dist ua_parser_rs
215+
- name: Run tests
216+
run: python -mpytest -v -Werror -ra ua-parser-rs
217+
218+
py-release:
219+
name: Release
220+
runs-on: ubuntu-latest
221+
needs: [py-release-tests, py-release-sdist]
222+
if: github.event_name == 'workflow_dispatch' && inputs.release
223+
permissions:
224+
# Use to sign the release artifacts
225+
id-token: write
226+
# Used to upload release artifacts
227+
contents: write
228+
# Used to generate artifact attestation
229+
attestations: write
230+
environment: release
231+
steps:
232+
- uses: actions/download-artifact@v4
233+
- name: Generate artifact attestation
234+
uses: actions/attest-build-provenance@v1
235+
with:
236+
subject-path: 'wheels-*/*'
237+
- name: Publish to PyPI
238+
uses: PyO3/maturin-action@v1
239+
with:
240+
command: upload
241+
args: --non-interactive --skip-existing wheels-*/*

0 commit comments

Comments
 (0)