Skip to content

Commit 6efd4dc

Browse files
authored
Merge pull request #236 from dimagi/dm/auto-pypi
Improve build and automate push to PyPI
2 parents 56eaafb + 1c0f052 commit 6efd4dc

11 files changed

Lines changed: 288 additions & 42 deletions

File tree

.github/workflows/pypi.yml

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
name: Build wheels and publish to PyPI
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
tags:
8+
- "v*"
9+
workflow_dispatch:
10+
11+
jobs:
12+
configure:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- name: Read Python versions from pyproject.toml
17+
id: read-versions
18+
# produces output like: python_versions=39,310,311,312,313
19+
run: >-
20+
echo "python_versions=$(
21+
grep -oP '(?<=Language :: Python :: )\d.\d+' pyproject.toml
22+
| sed 's/\.//'
23+
| tr '\n' ','
24+
| sed 's/,$//'
25+
)" >> $GITHUB_OUTPUT
26+
outputs:
27+
python_versions: ${{ steps.read-versions.outputs.python_versions }}
28+
29+
build_sdist:
30+
name: Build source distribution
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v4
34+
- name: Check version
35+
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
36+
run: python version.py check "${{ github.ref }}"
37+
- name: Add untagged version suffix
38+
if: ${{ ! startsWith(github.ref, 'refs/tags/v') }}
39+
run: python version.py update
40+
- name: Build sdist
41+
run: pipx run build --sdist
42+
- uses: actions/upload-artifact@v4
43+
with:
44+
name: sdist
45+
path: dist
46+
47+
choose_linux_wheel_types:
48+
name: Decide which wheel types to build
49+
runs-on: ubuntu-latest
50+
steps:
51+
- id: manylinux_x86_64
52+
run: echo "wheel_types=manylinux_x86_64" >> $GITHUB_OUTPUT
53+
- id: musllinux_x86_64
54+
run: echo "wheel_types=musllinux_x86_64" >> $GITHUB_OUTPUT
55+
outputs:
56+
wheel_types: ${{ toJSON(steps.*.outputs.wheel_types) }}
57+
58+
build_linux_wheels:
59+
needs: [configure, choose_linux_wheel_types, build_sdist]
60+
name: ${{ matrix.wheel_type }} wheels
61+
runs-on: ubuntu-latest
62+
strategy:
63+
matrix:
64+
wheel_type: ${{ fromJSON(needs.choose_linux_wheel_types.outputs.wheel_types) }}
65+
steps:
66+
- uses: actions/download-artifact@v4
67+
with:
68+
name: sdist
69+
path: dist
70+
- name: Extract sdist
71+
run: |
72+
tar zxvf dist/*.tar.gz --strip-components=1
73+
- uses: docker/setup-qemu-action@v3
74+
if: runner.os == 'Linux'
75+
name: Set up QEMU
76+
- name: Build wheels
77+
uses: pypa/cibuildwheel@v2.22.0
78+
env:
79+
CIBW_BUILD: cp{${{ needs.configure.outputs.python_versions }}}-${{ matrix.wheel_type }}
80+
CIBW_ARCHS_LINUX: auto
81+
CIBW_BEFORE_BUILD: cd {project}; pip install -e . # is there a better way to build the .so files?
82+
CIBW_TEST_COMMAND: cd {project}; python -m unittest
83+
- uses: actions/upload-artifact@v4
84+
with:
85+
name: ${{ matrix.wheel_type }}-wheels
86+
path: ./wheelhouse/*.whl
87+
88+
pypi-publish:
89+
name: Upload release to PyPI
90+
needs: [build_sdist, build_linux_wheels]
91+
runs-on: ubuntu-latest
92+
#if: startsWith(github.ref, 'refs/tags/v') # removed until pypi-test-publish is working
93+
environment:
94+
name: pypi
95+
url: https://pypi.org/p/jsonobject
96+
permissions:
97+
id-token: write
98+
steps:
99+
- name: Download all the dists
100+
uses: actions/download-artifact@v4
101+
with:
102+
# with no name set, it downloads all of the artifacts
103+
path: dist/
104+
- run: |
105+
mv dist/sdist/*.tar.gz dist/
106+
mv dist/*-wheels/*.whl dist/
107+
rmdir dist/{sdist,*-wheels}
108+
ls -R dist
109+
- name: Publish package distributions to PyPI
110+
uses: pypa/gh-action-pypi-publish@release/v1
111+
112+
# https://github.com/finpassbr/json-object/issues/1
113+
#pypi-test-publish:
114+
# name: Upload release to test PyPI
115+
# needs: [build_sdist, build_linux_wheels]
116+
# runs-on: ubuntu-latest
117+
# environment:
118+
# name: testpypi
119+
# url: https://test.pypi.org/p/jsonobject
120+
# permissions:
121+
# id-token: write
122+
# steps:
123+
# - name: Download all the dists
124+
# uses: actions/download-artifact@v4
125+
# with:
126+
# # with no name set, it downloads all of the artifacts
127+
# path: dist/
128+
# - run: |
129+
# mv dist/sdist/*.tar.gz dist/
130+
# mv dist/*-wheels/*.whl dist/
131+
# rmdir dist/{sdist,*-wheels}
132+
# ls -R dist
133+
# - name: Publish package distributions to PyPI
134+
# uses: pypa/gh-action-pypi-publish@release/v1
135+
# with:
136+
# repository-url: https://test.pypi.org/legacy/

.github/workflows/tests.yml

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,40 @@ on:
99
pull_request:
1010

1111
jobs:
12-
build:
12+
configure:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- name: Read Python versions from pyproject.toml
17+
id: read-versions
18+
# produces output like: python_versions=[ "3.9", "3.10", "3.11", "3.12" ]
19+
run: >-
20+
echo "python_versions=$(
21+
grep -oP '(?<=Language :: Python :: )\d\.\d+' pyproject.toml
22+
| jq --raw-input .
23+
| jq --slurp .
24+
| tr '\n' ' '
25+
)" >> $GITHUB_OUTPUT
26+
outputs:
27+
python_versions: ${{ steps.read-versions.outputs.python_versions }}
1328

29+
build:
30+
needs: [configure]
1431
runs-on: ubuntu-latest
1532
strategy:
1633
fail-fast: false
1734
matrix:
18-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
35+
python-version: ${{ fromJSON(needs.configure.outputs.python_versions) }}
1936

2037
steps:
21-
- uses: actions/checkout@v2
38+
- uses: actions/checkout@v4
2239
- name: Set up Python ${{ matrix.python-version }}
23-
uses: actions/setup-python@v2
40+
uses: actions/setup-python@v5
2441
with:
2542
python-version: ${{ matrix.python-version }}
2643
- name: Install dependencies
2744
run: |
2845
python -m pip install --upgrade pip
29-
python -m pip install setuptools
3046
python -m pip install -e .
3147
- name: Run tests
3248
run: |

CHANGES.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
No significant changes since the last release
66

77

8+
## 2.3.0
9+
10+
| Released on | Released by |
11+
|-------------|---------------|
12+
| 2025-02-24 | @millerdev |
13+
14+
- Improve build and automate push to PyPI (https://github.com/dimagi/jsonobject/pull/236)
15+
- Add pyproject.toml to replace most of setup.py
16+
- Automate python version matrix on gitub actions
17+
- Update github action versions
18+
- Publish releases to pypi.org
19+
- Build C files with Cython 3.0.12 (https://github.com/dimagi/jsonobject/pull/235)
20+
- Add support for Python 3.13
21+
822
## 2.2.0
923

1024
| Released on | Released by |

LIFECYCLE.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ This section contains instructions for the Dimagi team member performing the rel
3838

3939
## Bump version & update CHANGES.md
4040

41-
In a single PR, bump the version number and update CHANGES.md to include release notes for this new version.
41+
In a single PR, bump the version number in `jsonobject/__init__.py` and update
42+
CHANGES.md to include release notes for this new version.
4243

4344
### Pick a version number
4445

@@ -62,6 +63,9 @@ Once this PR is reviewed and merged, move on to the steps to release the update
6263

6364
## Release the new version
6465

65-
To push the package to pypi, we follow Dimagi's internal documentation.
66-
Follow the steps in https://confluence.dimagi.com/display/saas/Python+Packaging+Crash+Course
67-
to release.
66+
To push the package to pypi, create a git tag named "vX.Y.Z" using the version
67+
in `jsonobject/__init__.py` and push it to Github.
68+
69+
A dev release is pushed to pypi.com/p/jsonobject/#history on each push/merge to
70+
master. A dev release may also be published on-demand for any branch with
71+
[workflow dispatch](https://github.com/dimagi/jsonobject/actions/workflows/pypi.yml).

__init__.py

Whitespace-only changes.

jsonobject/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .properties import *
33
from .api import JsonObject
44

5+
__version__ = '2.3.0'
56
__all__ = [
67
'IntegerProperty', 'FloatProperty', 'DecimalProperty',
78
'StringProperty', 'BooleanProperty',

pyproject.toml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[project]
2+
name = "jsonobject"
3+
description = "A library for dealing with JSON as python objects"
4+
authors = [{name = "Danny Roberts", email = "droberts@dimagi.com"}]
5+
license = {file = "LICENSE"}
6+
readme = {file = "README.md", content-type = "text/markdown"}
7+
dynamic = ["version"]
8+
requires-python = ">= 3.9"
9+
classifiers = [
10+
"Programming Language :: Python",
11+
"Programming Language :: Python :: 3",
12+
13+
# The following classifiers are parsed by Github Actions workflows.
14+
# Precise formatting is important (no extra spaces, etc.)
15+
"Programming Language :: Python :: 3.9",
16+
"Programming Language :: Python :: 3.10",
17+
"Programming Language :: Python :: 3.11",
18+
"Programming Language :: Python :: 3.12",
19+
"Programming Language :: Python :: 3.13",
20+
21+
"License :: OSI Approved :: BSD License",
22+
]
23+
24+
[project.urls]
25+
Home = "https://github.com/dimagi/jsonobject"
26+
27+
[build-system]
28+
requires = [
29+
"setuptools>=75",
30+
"Cython>=3.0.0,<4.0.0",
31+
]
32+
build-backend = "setuptools.build_meta"
33+
34+
[tool.setuptools]
35+
packages = ["jsonobject"]
36+
37+
[tool.setuptools.dynamic]
38+
version = {attr = "jsonobject.__version__"}

scripts/install_cython.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#! /bin/bash
22

3-
# grep setup.py for the pinned version of cython
4-
PINNED_CYTHON=$(grep -oE 'cython>=[0-9]+\.[0-9]+\.[0-9]+,<[0-9]+\.[0-9]+\.[0-9]+' setup.py)
3+
# grep pyproject.toml for the pinned version of cython
4+
PINNED_CYTHON=$(grep -oE 'Cython>?=[^"]+' pyproject.toml)
55

6-
pip install $PINNED_CYTHON
6+
echo "Installing $PINNED_CYTHON"
7+
pip install $PINNED_CYTHON setuptools

setup.cfg

Lines changed: 0 additions & 2 deletions
This file was deleted.

setup.py

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from setuptools import setup
2-
import io
32

43
from setuptools.extension import Extension
54

@@ -20,40 +19,15 @@
2019
Extension('jsonobject.utils', ["jsonobject/utils" + ext],),
2120
]
2221

23-
CYTHON_REQUIRES = ['cython>=3.0.0,<4.0.0']
2422
if USE_CYTHON:
2523
from Cython.Build import cythonize
2624
extensions = cythonize(extensions, compiler_directives={"language_level" : "3str"})
2725
else:
2826
print("You are running without Cython installed. It is highly recommended to run\n"
29-
" pip install {}\n"
30-
"before you continue".format(' '.join(CYTHON_REQUIRES)))
31-
32-
33-
with io.open('README.md', 'rt', encoding="utf-8") as readme_file:
34-
long_description = readme_file.read()
35-
27+
" ./scripts/install_cython.sh\n"
28+
"before you continue")
3629

3730
setup(
3831
name='jsonobject',
39-
version='2.2.0',
40-
author='Danny Roberts',
41-
author_email='droberts@dimagi.com',
42-
description='A library for dealing with JSON as python objects',
43-
long_description=long_description,
44-
long_description_content_type='text/markdown',
45-
url='https://github.com/dimagi/jsonobject',
46-
packages=['jsonobject'],
47-
setup_requires=CYTHON_REQUIRES,
4832
ext_modules=extensions,
49-
classifiers=[
50-
'Programming Language :: Python',
51-
'Programming Language :: Python :: 3',
52-
'Programming Language :: Python :: 3.9',
53-
'Programming Language :: Python :: 3.10',
54-
'Programming Language :: Python :: 3.11',
55-
'Programming Language :: Python :: 3.12',
56-
'Programming Language :: Python :: 3.13',
57-
'License :: OSI Approved :: BSD License',
58-
],
5933
)

0 commit comments

Comments
 (0)