Skip to content

Commit 7eb82d5

Browse files
author
Patrick J. McNerthney
committed
Add PyPi build and publish
1 parent 3f77e4a commit 7eb82d5

File tree

8 files changed

+192
-35
lines changed

8 files changed

+192
-35
lines changed

.github/workflows/ci.yaml

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ on:
1414

1515
env:
1616
# Common versions
17-
PYTHON_VERSION: '3.12.11' # TODO: Used?
17+
PYTHON_VERSION: '3.13.7' # TODO: Used?
18+
HATCH_VERSION: '1.12.0'
1819
DOCKER_BUILDX_VERSION: 'v0.26.1'
1920

2021
# These environment variables are important to the Crossplane CLI install.sh
@@ -32,6 +33,9 @@ env:
3233
# XPKG: xpkg.upbound.io/${{ github.repository}}
3334
CROSSPLANE_REGORG: ghcr.io/${{ github.repository}}
3435

36+
# The PyPi project version to push. The default is v0.0.0+gitdate-gitsha.
37+
PYPI_VERSION: ${{ inputs.version }}
38+
3539
# The package version to push. The default is 0.0.0-gitsha.
3640
XPKG_VERSION: ${{ inputs.version }}
3741

@@ -86,11 +90,49 @@ jobs:
8690
#remove-link-from-badge: false
8791
#unique-id-for-comment: python3.8
8892

93+
build-pypi:
94+
runs-on: ubuntu-24.04
95+
steps:
96+
- name: Checkout
97+
uses: actions/checkout@v4
98+
99+
- name: Setup Python
100+
uses: actions/setup-python@v5
101+
with:
102+
python-version: ${{ env.PYTHON_VERSION }}
103+
104+
- name: Setup Hatch
105+
run: pipx install hatch==${{ env.HATCH_VERSION }}
106+
107+
# If a version wasn't explicitly passed as a workflow_dispatch input we
108+
# default to version v0.0.0+<git-commit-date>-<git-short-sha>, for example
109+
# v0.0.0+20231101115142-1091066df799. This is a simple implementation of
110+
# Go's pseudo-versions: https://go.dev/ref/mod#pseudo-versions.
111+
- name: Set Default PyPI Project Version
112+
if: env.PYPI_VERSION == ''
113+
run: echo "PYPI_VERSION=v0.0.0+$(date -d@$(git show -s --format=%ct) +%Y%m%d%H%M%S)-$(git rev-parse --short=12 HEAD)" >> $GITHUB_ENV
114+
115+
- name: Set PyPI Project Version
116+
run: hatch version ${{ env.PYPI_VERSION }}
117+
118+
- name: Build Sdist and Wheel
119+
run: hatch build
120+
121+
- name: Upload Sdist and Wheel to GitHub
122+
uses: actions/upload-artifact@v4
123+
with:
124+
name: dist
125+
path: "dist/*"
126+
if-no-files-found: error
127+
retention-days: 1
128+
89129
# We want to build most packages for the amd64 and arm64 architectures. To
90130
# speed this up we build single-platform packages in parallel. We then upload
91131
# those packages to GitHub as a build artifact. The push job downloads those
92132
# artifacts and pushes them as a single multi-platform package.
93-
build:
133+
build-xpkg:
134+
needs:
135+
- build-pypi
94136
runs-on: ubuntu-24.04
95137
strategy:
96138
fail-fast: true
@@ -113,6 +155,13 @@ jobs:
113155
- name: Checkout
114156
uses: actions/checkout@v4
115157

158+
- name: Download Wheel from GitHub
159+
uses: actions/download-artifact@v5
160+
with:
161+
name: dist
162+
pattern: '*.whl'
163+
path: dist
164+
116165
# We ask Docker to use GitHub Action's native caching support to speed up
117166
# the build, per https://docs.docker.com/build/cache/backends/gha/.
118167
- name: Build Runtime
@@ -142,16 +191,38 @@ jobs:
142191
if-no-files-found: error
143192
retention-days: 1
144193

194+
publish-pypi:
195+
# Don't publish unless we were run with an explicit version.
196+
if: ${{ inputs.version != '' }}
197+
needs:
198+
- build-xpkg # only publish if xpkg builds succeeds
199+
runs-on: ubuntu-24.04
200+
steps:
201+
- name: Download Sdist and Wheel from GitHub
202+
uses: actions/download-artifact@v5
203+
with:
204+
name: dist
205+
path: dist
206+
207+
- name: Setup Hatch
208+
run: pipx install hatch==${{ env.HATCH_VERSION }}
209+
210+
- name: Publish to PyPI
211+
env:
212+
HATCH_INDEX_USER: __token__
213+
HATCH_INDEX_AUTH: ${{ secrets.PYPI_API_TOKEN }}
214+
run: hatch publish --no-prompt
215+
145216
# This job downloads the single-platform packages built by the build job, and
146217
# pushes them as a multi-platform package. We only push the package it the
147218
# XPKG_ACCESS_ID and XPKG_TOKEN secrets were provided.
148-
push:
219+
push-xpkg:
220+
needs:
221+
- build-xpkg
149222
runs-on: ubuntu-24.04
150223
permissions:
151224
contents: read
152225
packages: write
153-
needs:
154-
- build
155226
steps:
156227
- name: Checkout
157228
uses: actions/checkout@v4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,4 @@ __marimo__/
212212

213213
# function-pythonic
214214
pythonic-packages/
215+
crossplane/pythonic/__version__.py

Dockerfile

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
FROM python:3.13-slim-trixie AS image
22

3-
WORKDIR /root/pythonic
4-
COPY pyproject.toml /root/pythonic
5-
COPY crossplane /root/pythonic/crossplane
3+
COPY dist/*.whl /root
64
WORKDIR /
75
RUN \
86
set -eux && \
9-
cd /root/pythonic && \
10-
pip install --root-user-action ignore --no-build-isolation setuptools==80.9.0 && \
11-
pip install --root-user-action ignore --no-build-isolation . && \
12-
pip uninstall --root-user-action ignore --yes setuptools && \
13-
cd .. && \
14-
rm -rf .cache pythonic && \
7+
pip install --root-user-action ignore --no-build-isolation /root/*.whl && \
8+
rm -rf /root/*.whl /root/.cache && \
159
groupadd --gid 2000 pythonic && \
1610
useradd --uid 2000 --gid pythonic --home-dir /opt/pythonic --create-home --shell /usr/sbin/nologin pythonic
1711

README.md

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ metadata:
5959
spec:
6060
package: ghcr.io/fortra/function-pythonic:v0.0.7
6161
```
62-
6362
## Composed Resource Dependencies
6463
6564
function-pythonic automatically handles dependencies between composed resources.
@@ -301,6 +300,84 @@ spec:
301300
self.status.composite = 'Hello, World!'
302301
```
303302

303+
## Quick Start Development
304+
305+
The following example demonstrates how to locally render function-python
306+
compositions. First, install the `crossplane-function-pythonic` python
307+
package into the python environment:
308+
```shell
309+
$ pip install crossplane-function-pythonic
310+
```
311+
Then create the following files:
312+
#### xr.yaml
313+
```yaml
314+
apiVersion: pythonic.fortra.com/v1alpha1
315+
kind: Hello
316+
metadata:
317+
name: world
318+
spec:
319+
who: World
320+
```
321+
#### composition.yaml
322+
```yaml
323+
apiVersion: apiextensions.crossplane.io/v1
324+
kind: Composition
325+
metadata:
326+
name: hellos.pythonic.fortra.com
327+
spec:
328+
compositeTypeRef:
329+
apiVersion: pythonic.fortra.com/v1alpha1
330+
kind: Hello
331+
mode: Pipeline
332+
pipeline:
333+
- step: pythonic
334+
functionRef:
335+
name: function-pythonic
336+
input:
337+
apiVersion: pythonic.fn.fortra.com/v1alpha1
338+
kind: Composite
339+
composite: |
340+
class Composite(BaseComposite):
341+
def compose(self):
342+
self.status.greeting = f"Hello, {self.spec.who}!"
343+
```
344+
#### functions.yaml
345+
apiVersion: pkg.crossplane.io/v1beta1
346+
kind: Function
347+
metadata:
348+
name: function-pythonic
349+
annotations:
350+
render.crossplane.io/runtime: Development
351+
spec:
352+
package: ghcr.io/fortra/function-pythonic:v0.0.7
353+
```
354+
In one terminal session, run function-pythonic:
355+
```shell
356+
$ function-pythonic --insecure --debug
357+
[2025-08-21 15:32:37.966] grpc._cython.cygrpc [DEBUG ] Using AsyncIOEngine.POLLER as I/O engine
358+
```
359+
In other terminal session, render the Composite:
360+
```shell
361+
$ crossplane render xr.yaml composition.yaml functions.yaml
362+
---
363+
apiVersion: pythonic.fortra.com/v1alpha1
364+
kind: Hello
365+
metadata:
366+
name: world
367+
status:
368+
conditions:
369+
- lastTransitionTime: "2024-01-01T00:00:00Z"
370+
reason: Available
371+
status: "True"
372+
type: Ready
373+
- lastTransitionTime: "2024-01-01T00:00:00Z"
374+
message: All resources are composed
375+
reason: AllComposed
376+
status: "True"
377+
type: ResourcesComposed
378+
greeting: Hello, World!
379+
```
380+
304381
## ConfigMap Packages
305382

306383
ConfigMap based python packages are enable using the `--packages` and

crossplane/pythonic/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import base64
23

34
from .composite import BaseComposite

crossplane/pythonic/__version__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# This is set at build time, using "hatch version"
2+
__version__ = "0.0.0"

crossplane/pythonic/function.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -220,30 +220,30 @@ async def run_function(self, request):
220220

221221
def trimFullName(self, name):
222222
name = name.split('.')
223-
ix = 0
224223
for values in (
225-
('request', 'response'),
226-
('observed', 'desired'),
227-
('resources', 'extra_resources'),
228-
None,
229-
('resource', 'items'),
224+
('request', 'observed', 'resources', None, 'resource'),
225+
('request', 'extra_resources', None, 'items', 'resource'),
226+
('response', 'desired', 'resources', None, 'resource'),
230227
):
231-
if values:
232-
if ix < len(name):
228+
if len(values) <= len(name):
229+
for ix, value in enumerate(values):
230+
if value and value != name[ix] and not name[ix].startswith(f"{value}["):
231+
break
232+
else:
233+
ix = 0
233234
for value in values:
234-
if name[ix] == value:
235-
del name[ix]
236-
break
237-
if name[ix].startswith(f"{value}["):
238-
if ix:
235+
if value:
236+
if value == name[ix]:
237+
del name[ix]
238+
elif ix:
239239
name[ix-1] += name[ix][len(value):]
240240
del name[ix]
241241
else:
242242
name[ix] = name[ix][len(value):]
243243
ix += 1
244-
break
245-
else:
246-
ix += 1
244+
else:
245+
ix += 1
246+
break
247247
return '.'.join(name)
248248

249249

pyproject.toml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
[project]
22
name = "crossplane-function-pythonic"
3-
version = "0.0.6"
43
description = 'A Python centric Crossplane Function'
54
readme = "README.md"
65
requires-python = ">=3.11,<3.14"
@@ -21,14 +20,26 @@ dependencies = [
2120
"pyyaml==6.0.2",
2221
]
2322

23+
dynamic = ["version"]
24+
2425
[project.urls]
2526
Documentation = "https://github.com/fortra/function-pythonic#readme"
2627
Issues = "https://github.com/fortra/function-pythonic/issues"
2728
Source = "https://github.com/fortra/function-pythonic"
2829

29-
[tool.setuptools]
30-
packages = ["crossplane.pythonic"]
31-
py-modules = []
30+
[project.scripts]
31+
function-pythonic = "crossplane.pythonic.main:main"
32+
33+
[build-system]
34+
requires = ["hatchling"]
35+
build-backend = "hatchling.build"
36+
37+
[tool.hatch.version]
38+
path = "crossplane/pythonic/__version__.py"
39+
validate-bump = false
40+
41+
[tool.hatch.build.targets.wheel]
42+
only-include = ["crossplane"]
3243

3344
[tool.hatch.envs.default]
3445
type = "virtual"

0 commit comments

Comments
 (0)