Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ajparsons authored Sep 10, 2024
0 parents commit 1019c03
Show file tree
Hide file tree
Showing 24 changed files with 655 additions and 0 deletions.
93 changes: 93 additions & 0 deletions .github/workflows/auto_publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Tools to publish package to pypi automatically
# on update of poetry version.
# Will also update tags on automatic release.

name: "Publish package"

# don't allow multiple 'identical' processes to run. A second push should cancel the job from the first one.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ github.event.inputs.pypi }}-${{ github.event.inputs.testpypi }}
cancel-in-progress: true

on:
workflow_dispatch:
inputs:
pypi:
description: Force to pypi
type: boolean
default: false
testpypi:
description: Force to testpypi
type: boolean
default: false
push:
branches: [main]

jobs:

# run the tests first, if this fails nothing continues
test:
uses: ./.github/workflows/test.yml

# run auto either if nothing explicit forced in workflow or it is a push event
publish-auto:
if: ${{ (github.event.inputs.testpypi == 'false' && github.event.inputs.pypi == 'false') || github.event_name == 'push' }}
needs: test
runs-on: ubuntu-latest
steps:

- uses: actions/checkout@v2

- name: Fetch repo name
id: repo_name
run: echo "::set-output name=value::$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')"

- id: get_status
name: get_status
uses: ajparsons/compare-pypi-poetry-version@v1
with:
package_name: ${{ steps.repo_name.outputs.value }}

- name: Update git tags
if: ${{ steps.get_status.outputs.remote_exists == 'true' && steps.get_status.outputs.version_difference == 'true'}}
shell: bash
run: |
git config --global user.email "[email protected]"
git config --global user.name "GitHub Action"
git tag -f -a -m "Latest release" "latest"
for val in $TAGS; do
git tag -f -a -m "Release for $val" "$val"
done
git push -f --tags
env:
TAGS: ${{ steps.get_status.outputs.version_tags }}

- name: Build and publish to pypi
if: ${{ steps.get_status.outputs.remote_exists == 'true' && steps.get_status.outputs.version_difference == 'true'}}
uses: JRubics/[email protected]
with:
pypi_token: ${{ secrets.PYPI_TOKEN }}

# run manual if one of the boolean buttons for workflow was used
# this can force the initial creation of the package
publish-manual:
if: ${{ github.event.inputs.testpypi == 'true' || github.event.inputs.pypi == 'true' }}
needs: test
runs-on: ubuntu-latest
steps:

- uses: actions/checkout@v2

- name: Build and publish to pypi
if: ${{ github.event.inputs.pypi == 'true' }}
uses: JRubics/[email protected]
with:
pypi_token: ${{ secrets.PYPI_TOKEN }}

- name: Build and publish to testpypi
if: ${{ github.event.inputs.testpypi == 'true' }}
uses: JRubics/[email protected]
with:
pypi_token: ${{ secrets.TEST_PYPI_TOKEN }}
repository_name: "testpypi"
repository_url: "https://test.pypi.org/legacy/"
87 changes: 87 additions & 0 deletions .github/workflows/template_setup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# From https://raw.githubusercontent.com/simonw/python-lib-template-repository/main/.github/workflows/setup.yml
name: Execute template to populate repository

on:
push:
workflow_dispatch:

permissions:
actions: write
contents: write

jobs:
setup-repo:
if: ${{ !endsWith(github.repository, '-auto-template') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
ref: ${{ github.head_ref }}

- name: Install cookiecutter
run: pip3 install cookiecutter

- uses: actions/github-script@v4
id: fetch-repo-and-user-details
with:
script: |
const query = `query($owner:String!, $name:String!) {
repository(owner:$owner, name:$name) {
name
description
owner {
login
... on User {
name
}
... on Organization {
name
}
}
}
}`;
const variables = {
owner: context.repo.owner,
name: context.repo.repo
}
const result = await github.graphql(query, variables)
console.log(result)
return result
- name: Rebuild contents using cookiecutter
env:
INFO: ${{ steps.fetch-repo-and-user-details.outputs.result }}
run: |
export REPO_NAME=$(echo $INFO | jq -r '.repository.name')
# Run cookiecutter
pushd /tmp
cookiecutter $GITHUB_WORKSPACE --no-input \
lib_name=$REPO_NAME \
description="$(echo $INFO | jq -r .repository.description)" \
github_username="$(echo $INFO | jq -r .repository.owner.login)" \
author_name="$(echo $INFO | jq -r .repository.owner.name)" \
author_email="${{ github.event.pusher.email }}" \
github_id=$GITHUB_REPOSITORY
popd
# Move generated content to root directory of repo
rm -r tests
rm -r hooks
mv /tmp/$REPO_NAME/* .
# And hidden settings files:
mv /tmp/$REPO_NAME/.gitignore .
mv /tmp/$REPO_NAME/.devcontainer .
mv /tmp/$REPO_NAME/.vscode .
# Delete the template related workflow and the cookiecutter files
rm .github/workflows/template_setup.yml
rm .github/workflows/template_test.yml
rm -r "{{cookiecutter.hyphenated}}"
rm -r cookiecutter.json
rm pytest.ini
rm requirements.dev.txt
- name: Force push new repo contents
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "Initial library structure"
push_options: --force
24 changes: 24 additions & 0 deletions .github/workflows/template_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Run meta pytest suite on repo

on:
pull_request:
push:

jobs:
run-test:
if: ${{ endsWith(github.repository, '-auto-template') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
ref: ${{ github.head_ref }}

- name: Install Poetry
uses: snok/install-poetry@v1

- name: Install cookiecutter
run: pip install -r requirements.dev.txt

- name: run pytest
run: python -m pytest
54 changes: 54 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Run tests

on:
push:
branches-ignore: [ main ]
pull_request:
branches-ignore: [ main ]
workflow_call:
workflow_dispatch:

jobs:
test:
if: ${{ !endsWith(github.repository, '-auto-template') }}
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10"]
poetry-version: ["1.8"]

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install poetry ${{ matrix.poetry-version }}
run: |
python -m ensurepip
python -m pip install --upgrade pip
python -m pip install poetry==${{ matrix.poetry-version }}
- name: Install dependencies
shell: bash
run: |
python -m poetry config virtualenvs.create false
python -m poetry install
- name: Test with pytest
shell: bash
run: |
python -m pytest -v tests
- name: Test with pyright
uses: jakebailey/pyright-action@v1

- name: Test with ruff
shell: bash
run: |
python -m ruff .
python -m ruff format --check .
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pyc
__pycache__
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Python project template

This template repository can be used to create a new repository with the skeleton of a poetry-based Python library.

This is based on the general approach of [simonw/python-lib](https://github.com/simonw/python-lib), but with a different default structure.

# Creating a new template

A new repository based on this template can be created in Github or through cookiecutter.

## Templating in GitHub

The templating process can be run entirely in GitHub to create a new repository.

Start here: https://github.com/ajparsons/python-poetry-auto-template/generate

Add a one-line description of your repository, then click "Create repository from template".

# Templating with cookiecutter

This repo can also be used to set up a template offline using [cookiecutter](https://cookiecutter.readthedocs.io/en/stable/). To start the processs:

```
python -m cookiecutter https://github.com/ajparsons/python-poetry-auto-template/
```

# Features

The default package uses:

* [poetry](https://python-poetry.org/) for package management,
* [pytest](https://docs.pytest.org/en/7.1.x/) for testing,
* [black](https://black.readthedocs.io/en/stable/) for linting,
* [pyright](https://github.com/microsoft/pyright) for typechecking.
* [GitHub Actions](https://github.com/features/actions) for CI and publishing.

New repositories include config files and Dockerfile for developing in VS Code or Codespaces, so the development process can happen end to end in Github. (Or not! Will still worked cloned locally).

The test suite contains meta tests for alignment between the `__version__` of the package and the poetry version, and that the current version is documented in the change log.

The template version includes a default GitHub Action for testing on Python 3.8-3.10, and publishing to pypi.

By default, the test action requires pytest, black and pyright to return no errors.

The default licence is the MIT Licence. Change if needed.

# Publishing the package

* Set a GitHub Actions secret for PYPI_TOKEN.
* For the initial publish. In the Actions tab for a repo, trigger a manual workfork flow with the 'force to pypi' box ticked.
* Subsequently, if the poetry version is bumped and all tests pass - the GitHub Action will automatically publish on push to the main branch.

# Development and forking

If you want to modify or extend this approach - the self-bootstrapping behaviour will only happen when a repo does *not* end in '-auto-template'. If you clone this repo, into a different user or org space, it will not self-bootstrap because the name is the same.

If you wanted to extend this into a basic django template - you might fork or clone the repo and call it 'django-auto-template'. This will not self-bootstrap, but new projects created from that template would.

## Meta tests

This project defines several meta tests in `tests/` that will:

* Attempt to provision a template with basic variables.
* Run the projects internal tests for self-integrity.

This requires the packages listed in `requirements.dev.txt` to be installed.
12 changes: 12 additions & 0 deletions cookiecutter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"lib_name": "",
"description": "",
"hyphenated": "{{ '-'.join(cookiecutter['lib_name'].lower().split()).replace('_', '-') }}",
"underscored": "{{ cookiecutter.hyphenated.replace('-', '_') }}",
"github_username": "",
"author_name": "",
"author_email": "",
"year":"{% now 'utc', '%Y' %}",
"yyyy_mm_dd":"{% now 'utc', '%Y-%m-%d' %}",
"github_id": "{{cookiecutter.github_username}}/{{cookiecutter.lib_name}}"
}
24 changes: 24 additions & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import shutil
import os
from pathlib import Path

# transpose github workflows into cookiecutter after generate
# there's a problem where a github actions self template can't
# modify an action, so we instead have all actions at the top - so
# they are only removed, and not 'modified' by the templating process.
# this hook makes sure when used as a cookiecutter - the actions are loaded correctly.

template_dir = r"{{ cookiecutter._template }}"
if any(x in template_dir for x in ["https:", "gh:"]):
repo_name = template_dir.split("/")[-1]
template_dir = Path.home() / ".cookiecutters" / repo_name
else:
template_dir = Path(template_dir)

workflow_dir_source = template_dir / ".github" / "workflows"
workflow_dir_dest = Path(os.getcwd()) / ".github" / "workflows"

workflow_dir_dest.mkdir(parents=True, exist_ok=True)
for item in workflow_dir_source.glob("*.yml"):
if item.name.startswith("template_") is False:
shutil.copyfile(item, workflow_dir_dest / item.name)
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# pytest.ini
[pytest]
testpaths =
tests
3 changes: 3 additions & 0 deletions requirements.dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest==7.1.3
cookiecutter==2.1.0
requests==2.28.1
Loading

0 comments on commit 1019c03

Please sign in to comment.