From 7e34654f44c80b7f77960c895b794ed672dc5752 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 09:54:20 +0200 Subject: [PATCH 01/22] :art: add flake8 and ruff line length default (to 90 characters) --- pyproject.toml | 5 ++++- setup.cfg | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index f0cf6dc..c7554d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,10 @@ dev = ["black", "ruff", "pytest"] # Configure the Ruff linter: Ignore error number 501 [tool.ruff] -lint.ignore = ["E501"] +# https://docs.astral.sh/ruff/rules/#flake8-bandit-s +lint.ignore = ["E501"] # Ignore line length errors +# Allow lines to be as long as: +line-length = 90 [build-system] build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..12062ee --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[flake8] +exclude = docs +max-line-length = 90 +aggressive = 2 From 0ed57cd8e9acb115345b7438784ad60057c16e48 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 11:07:40 +0200 Subject: [PATCH 02/22] :art: add formatting of jupyter notebooks per default --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c7554d2..4cb9da4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ docs = [ "jupytext", ] # local development options -dev = ["black", "ruff", "pytest"] +dev = ["black[jupyter]", "ruff", "pytest"] # Configure the Ruff linter: Ignore error number 501 [tool.ruff] From 453a4fb63cde7d80912c42d76e4fe5afa6c43eed Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 11:08:08 +0200 Subject: [PATCH 03/22] :memo: highlight that also a folder has to be renamed --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4e71da5..0dd231f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ see [GitHub documentation](https://docs.github.com/en/repositories/creating-and- You will need to find and replace occurences of - `python_package` -> `your_package_name` + - also the folder `src/python_package` - `RasmussenLab` -> `GitHub_user_name` (or `organization`) with the name of your package and GitHub user name (or organization). From 8d4ae8b8e8ac5ca864675c9172c87923e6ff81fd Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 11:08:31 +0200 Subject: [PATCH 04/22] :memo: document what extension are used for --- docs/conf.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 12ccf81..3e227bf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,13 +29,13 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.autodoc.typehints", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.intersphinx", - "sphinx_new_tab_link", - "myst_nb", + "sphinx.ext.autodoc", # Core extension for generating documentation from docstrings + "sphinx.ext.autodoc.typehints", # Automatically document type hints in function signatures + "sphinx.ext.viewcode", # Include links to the source code in the documentation + "sphinx.ext.napoleon", # Support for Google and NumPy style docstrings + "sphinx.ext.intersphinx", # allows linking to other projects' documentation in API + "sphinx_new_tab_link", # each link opens in a new tab + "myst_nb", # Markdown and Jupyter Notebook support ] # https://myst-nb.readthedocs.io/en/latest/computation/execute.html @@ -43,7 +43,7 @@ myst_enable_extensions = ["dollarmath", "amsmath"] -# Plolty support through require javascript library +# Plotly support through require javascript library # https://myst-nb.readthedocs.io/en/latest/render/interactive.html#plotly html_js_files = [ "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js" From 0f3d369bc5a026f79ebad92718a6a4d2418f0677 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 11:18:51 +0200 Subject: [PATCH 05/22] :memo: document debian (ubuntu) package adding which might be needed --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 75f0fc0..29f28ee 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -14,6 +14,9 @@ build: # nodejs: "19" # rust: "1.64" # golang: "1.19" + # apt_packages: + # - some_package + # Build documentation in the "docs/" directory with Sphinx sphinx: From 5df3ae38f916ae0a59933ba09bedc460482e105d Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 11:35:23 +0200 Subject: [PATCH 06/22] :art: use PyPI and GitHub integration instead of secret api token --- .github/workflows/cicd.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index f06e9ed..81ffcf3 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -119,10 +119,9 @@ jobs: with: name: artifact path: ./dist - + # register PyPI integration: + # https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ - uses: pypa/gh-action-pypi-publish@release/v1 with: # remove repository key to set the default to pypi (not test.pypi.org) repository-url: https://test.pypi.org/legacy/ - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} From f162ae13a32df73ebe199c81224ed03ddf7d20cb Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 11:39:52 +0200 Subject: [PATCH 07/22] =?UTF-8?q?=F0=9F=99=88=20hide=20folders=20creating?= =?UTF-8?q?=20when=20locally=20building=20the=20documentation=20website?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/.gitignore diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..14a2c18 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,3 @@ +_build +reference +jupyter_execute From 0e93852b8e39a9220b04421182e6b8642c8010be Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 11:49:31 +0200 Subject: [PATCH 08/22] :memo: document desing choices and layout of Python package --- README.md | 2 + developing.md | 479 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 481 insertions(+) create mode 100644 developing.md diff --git a/README.md b/README.md index 0dd231f..59bcc49 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Example Python package +All design principles are explained in the [developing.md](developing.md) file. + ## How to use Can be used as GitHub template repository, diff --git a/developing.md b/developing.md new file mode 100644 index 0000000..d8370dc --- /dev/null +++ b/developing.md @@ -0,0 +1,479 @@ +# Python package template + +> Author: Henry Webel + +[packaging.python.org](https://packaging.python.org/en/latest/tutorials/packaging-projects/) +has an excellent tutorial on how to package a Python project. I read and used insights from +that website to help create the template which is available on GitHub at +[https://github.com/RasmussenLab/python_package](https://github.com/RasmussenLab/python_package) +and I want to give here an overview specifically to some details regarding this template. +Some are overlapping with the [packaging.python.org](https://packaging.python.org/en/latest/tutorials/packaging-projects/) +tutorial, but as always we decided for a certain set of tools, conventions and complexity +which needs some explanation. + +## Project structure + +First an overview of the main folder structure. See line comments for details on what +folder or file purpose. + +```bash +python_package +├── docs # Documentation using Sphinx +├── src # the source code of the package +├── tests # pytest tests +├── LICENSE # License file specifying usage terms +├── MANIFEST.in # non-python files to include into the build package +├── pyproject.toml # python package metadata, dependencies and configurations (incl. build tools) +├── pytest.ini # pytest configuration +├── README.md # README which is rendered on GitHub (or other hosting services) +└── setup.cfg # old python configuration file, only used to set flake8 line length +└── setup.py # artefact for backward compatibility, do not change +``` + +## Core packaging files + +We will first look at `pyproject.toml` and it's relation to the `src` directory. + +
+About setup.py and setup.cfg configuration files + +The `setup.py` file is an artefact for backward compatibility and should not be changed. +Everything that used to be in `setup.py` or `setup.cfg` is now largely in `pyproject.toml`. +The notable exception is specifying the desired maximum line length in `setup.cfg` via +the `flake8` section, which is not yet supported in `pyproject.toml`. We specify the line +length for ruff in `pyproject.toml` and for flake8 in `setup.cfg`. + +
+ +### Changes required in `pyproject.toml` + +You have to change entries under the `[project]` section to match your project name, +description, author, license, etc. The `dependencies` section can list the dependencies +and is currently commented out. The dependencies could also be specified in via a +`requirements.txt`, if you already have such a file. + +```toml +# ref: https://setuptools.pypa.io/en/stable/userguide/pyproject_config.html +[project] +authors = [ + { name = "Jakob Nybo Nissen", email = "jakobnybonissen@gmail.com" }, + { name = "Henry Webel", email = "henry.webel@sund.ku.dk" }, +] +description = "A small example package" +name = "python_package" +# This means: Load the version from the package itself. +# See the section below: [tools.setuptools.dynamic] +dynamic = ["version"] # version is loaded from the package +readme = "README.md" +requires-python = ">=3.9" +# These are keywords +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +# # add dependencies here: (use one of the two) +# dependencies = ["numpy", "pandas", "scipy", "matplotlib", "seaborn"] +# use requirements.txt instead of pyproject.toml for dependencies +# https://stackoverflow.com/a/73600610/9684872 +# [tool.setuptools.dynamic] +# dependencies = {file = ["requirements.txt"]} +``` + +The entry + +```toml +dynamic = ["version"] +``` + +means that the version is loaded dynamically using the extension +[setuptools_scm](https://setuptools-scm.readthedocs.io/) +we list under the `[build-system]` section in `pyproject.toml`. +This is done to avoid having to manually update the version and integrate with automatic +versioning through releases on GitHub. It also +ensures that each commit has a unique version number, which is useful for attributing +errors to specific non-released versions. The dynamic version is picked up in the +`__version__` variable in the `__init__.py` file of the package, which is located in the +`src/python_package` directory. + +```toml +[build-system] +build-backend = "setuptools.build_meta" +requires = ["setuptools>=64", "setuptools_scm>=8"] + +[tool.setuptools_scm] +# https://setuptools-scm.readthedocs.io/ +# used to pick up the version from the git tags or the latest commit. +``` + +Please also update the project URL to your project: + +```toml +[project.urls] +"Bug Tracker" = "https://github.com/RasmussenLab/python_package/issues" +"Homepage" = "https://github.com/RasmussenLab/python_package" +``` + +## Source directory layout of the package + +The source code of the package is located in the `src` directory, to have a project +independent folder to look for the source code. It also allows to have multiple packages +in the same project, although this is not our main use case. + +```bash +├── src +│ └── python_package +│ ├── __init__.py # imported when the package is imported (import python_package) +│ └── mockup.py # a submodule of the package (import python_package.mockup) +``` + +So you will need to rename the `python_package` directory to your package name, +e.g. `my_package` and specify the package name in the [`pyproject.toml`](pyproject.toml) file +under the `[project]` section: + +```toml +name = "my_package" +``` + +Strictly speaking you can give different names in both places, but this will only confuse +potential users. Think of `scikit-learn` for an example of a package that has a different +name in the [`pyproject.toml`](pyproject.toml) file and the source code directory name. + +## Documentation + +The documentation is created using [Sphinx](https://www.sphinx-doc.org/en/master/), +which is common for Python documentation. It relies additionally on several extensions +enabling the use of `markdown` and `jupyter` notebooks. + +The documentation is located in the [`docs`](docs) directory. Sphinx is configured via +the [`conf.py`](docs/conf.py) file, where you can specify the extension you want. + +```python +# in docs/conf.py + +extensions = [ + "sphinx.ext.autodoc", # Core extension for generating documentation from docstrings + "sphinx.ext.autodoc.typehints", # Automatically document type hints in function signatures + "sphinx.ext.viewcode", # Include links to the source code in the documentation + "sphinx.ext.napoleon", # Support for Google and NumPy style docstrings + "sphinx.ext.intersphinx", # allows linking to other projects' documentation in API + "sphinx_new_tab_link", # each link opens in a new tab + "myst_nb", # Markdown and Jupyter Notebook support +] +``` + +These are added as dependencies through the +`pyproject.toml` file under the `[project.optional-dependencies]` section: + +```toml +[project.optional-dependencies] +# Optional dependencies to locally build the documentation, also used for +# readthedocs. +docs = [ + "sphinx", + "sphinx-book-theme", + "myst-nb", + "ipywidgets", + "sphinx-new-tab-link!=0.2.2", + "jupytext", +] +``` + +The required changes in [`conf.py`](docs/conf.py) are at the following places: + +```python +# in docs/conf.py + +project = "python_package" +copyright = "2025, Jakob Nybo Nissen, Henry Webel" +author = "Jakob Nybo Nissen, Henry Webel" +PACKAGE_VERSION = metadata.version("python_package") + +# ... + +# and again links to your project repository +html_theme_options = { + "github_url": "https://github.com/RasmussenLab/python_package", + "repository_url": "https://github.com/RasmussenLab/python_package", + # more... +} + +# ... + +# and one last line (the last below) +if os.environ.get("READTHEDOCS") == "True": + from pathlib import Path + + PROJECT_ROOT = Path(__file__).parent.parent + PACKAGE_ROOT = PROJECT_ROOT / "src" / "python_package" +``` + +The last block is for Read The Docs to be able to generate the API documentation of your +package on the fly. See the Read The Docs section below for more details. + +We build the documentation based on the template +[sphinx_book_theme](https://sphinx-book-theme.readthedocs.io), which is set in the +[`conf.py`](docs/conf.py) file and parts of our docs requirements in +[`pyproject.toml`](pyproject.toml): + +```python +html_theme = "sphinx_book_theme" +``` + +> If you use a different theme, some of the settings in `conf.py` might not be applicable +> and need to be changed. Explore other themes here: +> [sphinx-themes.org](https://sphinx-themes.org/) + +The API of the Python package in the `src` directory is automatically included +in the documentation using the `autodoc` extension. The API documentation can be +augumented with hightlights from other types from projects using `intersphinx`: + +```python +# Intersphinx options +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + # "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), + # "scikit-learn": ("https://scikit-learn.org/stable/", None), + # "matplotlib": ("https://matplotlib.org/stable/", None), +} +``` + +Here we only add the core Python documentation, but you can add more projects +like `pandas`, `scikit-learn`, or `matplotlib` to the mapping. + +To build the documentation locally, you can follow the instructions in the +[`docs/README.md`](docs/README.md), which you should also update with your name changes. +In short, you can run the following commands in the `docs` directory: + +```bash +# in root of the project +pip install ".[docs]" +cd docs # change to docs directory +sphinx-apidoc --force --implicit-namespaces --module-first -o reference ../src/python_package +sphinx-build -n -W --keep-going -b html ./ ./_build/ +``` + +this will create a `reference` directory with the API documentation of the Python package +`python_package`, a `jupyter_execute` for the tutorial in `docs/tutorial` and a +`_build` directory with an HTML version of the documentation. You can open the +`_build/index.html` file in your browser to view the documentation built locally. + +The tutorial related configuration in `conf.py` is the following, specifying that +errors stop the build process ensuring that examples are tested: + +```python +# https://myst-nb.readthedocs.io/en/latest/computation/execute.html +nb_execution_mode = "auto" + +myst_enable_extensions = ["dollarmath", "amsmath"] + +# Plotly support through require javascript library +# https://myst-nb.readthedocs.io/en/latest/render/interactive.html#plotly +html_js_files = [ + "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js" +] + +# https://myst-nb.readthedocs.io/en/latest/configuration.html +# Execution +nb_execution_raise_on_error = True +# Rendering +nb_merge_streams = True +``` + +The tutorials are meant as a sort of integration test, where you make sure that the core +functionality your project wants to support is working as expected. For easier github +diffs, we use [`jupytext`](https://jupytext.readthedocs.io), which allows to +have the tutorial in both a Jupyter Notebook format and a Python script format. +You have to keep the files in sync using: + +```bash +jupytext --sync docs/tutorial/*.ipynb +``` + +The [`docs/tutorial/.jupytext`](docs/tutorial/.jupytext) configuration sets the default +format to `py:percent` and automatically allows syncing of new notebooks. + +### Read The Docs + +To build the documentation on Read The Docs, you need to create a file called +`.readthedocs.yaml`, which is located in the root of the project and specifies which +dependencies are needed. The core is the following specifying where the `conf.py` file +is and from where to install the required dependencies: + +```yaml +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +python: + install: + - method: pip + path: . + extra_requirements: + - docs +``` + +You will need to manually register your project repository on +[Read The Docs](https://readthedocs.org/) in order that it can build the documentation +by the service. I recommend to activate builds for Pull Requests, so that +the documentation is built for each PR and you can see if the documentation gradually +breaking and your work advancing. See their documentation +[on adding a project](https://docs.readthedocs.com/platform/stable/intro/add-project.html) +for instructions. + +## Running tests + +The tests are located in the `tests` directory and can be run using `pytest`. +Pytest is specified as a dependency in the `pyproject.toml` file under the +`[project.optional-dependencies]` section along with the formatter `black` and the +linter `ruff`: + +```toml +[project.optional-dependencies] +# local development options +dev = ["black[jupyter]", "ruff", "pytest"] +``` + +Instead of running these tools manually, typing: + +```bash +black . +ruff check . +pytest tests +``` + +Read the next section to see how this is automated using `GitHub Actions`. + +## GitHub Actions + +We run these checks also on GitHub using GitHub Actions. The configuration +for the actions is located in the [`.github/workflows`](.github/workflows) directory +and is specified in the `cdci.yml` file. See the biosustain dsp tutorial on GitHub Actions +for more details (or any other resource you find): +[biosustain/dsp_actions_tutorial](https://github.com/biosustain/dsp_actions_tutorial) + +```yaml +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + pull_request: + branches: ["main"] + schedule: + - cron: "0 2 * * 3" + +permissions: + contents: read + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: psf/black@stable + lint: + name: Lint with ruff + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install ruff + run: | + pip install ruff + - name: Lint with ruff + run: | + # stop the build if there are Python syntax errors or undefined names + ruff check . + test: + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" # caching pip dependencies + cache-dependency-path: "**/pyproject.toml" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + pip install -e . + - name: Run tests + run: python -m pytest tests +``` + +This files also allows to create `PyPI` releases automatically if you register your +project on `PyPI` (or `TestPyPI` for testing first) and create a GitHub release: + +```yaml + publish: + name: Publish package + if: startsWith(github.ref, 'refs/tags') + needs: + - format + - lint + - test + - build_source_dist + # - build_wheels + runs-on: ubuntu-latest + + steps: + - uses: actions/download-artifact@v4 + with: + name: artifact + path: ./dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + # remove repository key to set the default to pypi (not test.pypi.org) + repository-url: https://test.pypi.org/legacy/ +``` + +To setup the `gh-action-pypi-publish` action, you need to register the repository +on `PyPI` or `TestPyPI`, which allows PyPI and GitHub to communicate securely. +See the instructions on +[packaging.python.org](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/). + +> The wheels are not built by default, but you can be necessary for packages which need +> to be partly compiled, e.g. if you use `Cython`, `numpy` C extensions or Rust extensions. + +## Full project structure + +```bash +python_package +├── docs +│ ├── tutorial +│ │ ├── tutorial.ipynb # tutorial in Jupyter Notebook format +│ │ └── tutorial.py # tutorial in Python script format (created by jupytext) +│ ├── conf.py # configuration for Sphinx documentation +│ ├── index.md # defining the website structure +│ ├── Makefile # can be ignored +│ └── README.md # specifies how to build the documentation +├── src +│ └── python_package +│ ├── __init__.py # imported when the package is imported (import python_package) +│ └── mockup.py # a submodule of the package (import python_package.mockup) +├── tests +│ ├── __init__.py +│ └── test_mockup.py # files and test_function need to start with test_ to be recognized by pytest +├── LICENSE +├── MANIFEST.in +├── pyproject.toml +├── pytest.ini +├── README.md +├── setup.cfg # only used to set flake8 line length +└── setup.py +``` + + From e1026bd20718d0b5e9b0b72840d8df9344744c5f Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Wed, 25 Jun 2025 12:02:27 +0200 Subject: [PATCH 09/22] :art: add copy button for code blocks per default --- developing.md | 1 + docs/conf.py | 1 + pyproject.toml | 1 + 3 files changed, 3 insertions(+) diff --git a/developing.md b/developing.md index d8370dc..2ee2413 100644 --- a/developing.md +++ b/developing.md @@ -159,6 +159,7 @@ extensions = [ "sphinx.ext.intersphinx", # allows linking to other projects' documentation in API "sphinx_new_tab_link", # each link opens in a new tab "myst_nb", # Markdown and Jupyter Notebook support + "sphinx_copybutton", # add copy button to code blocks ] ``` diff --git a/docs/conf.py b/docs/conf.py index 3e227bf..b54becf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,7 @@ "sphinx.ext.intersphinx", # allows linking to other projects' documentation in API "sphinx_new_tab_link", # each link opens in a new tab "myst_nb", # Markdown and Jupyter Notebook support + "sphinx_copybutton", # add copy button to code blocks ] # https://myst-nb.readthedocs.io/en/latest/computation/execute.html diff --git a/pyproject.toml b/pyproject.toml index 4cb9da4..a247dcf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ docs = [ "ipywidgets", "sphinx-new-tab-link!=0.2.2", "jupytext", + "sphinx-copybutton", ] # local development options dev = ["black[jupyter]", "ruff", "pytest"] From a5b2ac308e02aa3745051978f40a55f0f5a53c61 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Fri, 27 Jun 2025 09:19:02 +0200 Subject: [PATCH 10/22] :art: small corrections after proof-reading --- developing.md | 92 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/developing.md b/developing.md index 2ee2413..8ea7ad9 100644 --- a/developing.md +++ b/developing.md @@ -13,8 +13,8 @@ which needs some explanation. ## Project structure -First an overview of the main folder structure. See line comments for details on what -folder or file purpose. +First an overview of the main folder structure. See line comments for details on what +is the purpose of each folder or file: ```bash python_package @@ -32,23 +32,30 @@ python_package ## Core packaging files -We will first look at `pyproject.toml` and it's relation to the `src` directory. +We will first look at [`pyproject.toml`] and it's relation to the [`src`](src) directory. The +[`project.toml`](pyproject.toml) file is the main configuration file for the Python package +and is used to specify the package metadata, dependencies, build tools and configurations. +The [`src`](src) folder stores the actual source code of the package, where the package itself is +the subdirectories of the [`src`](src) directory. The (e.g. `src/python_package`).
About setup.py and setup.cfg configuration files -The `setup.py` file is an artefact for backward compatibility and should not be changed. -Everything that used to be in `setup.py` or `setup.cfg` is now largely in `pyproject.toml`. +The [`setup.py`](setup.py) file is an artefact for backward compatibility and should not +be changed. Everything that used to be in [`setup.py`](setup.py) or +[`setup.cfg`](setup.cfg) is now largely in [`pyproject.toml`](pyproject.toml). The notable exception is specifying the desired maximum line length in `setup.cfg` via -the `flake8` section, which is not yet supported in `pyproject.toml`. We specify the line -length for ruff in `pyproject.toml` and for flake8 in `setup.cfg`. +the [`flake8`](https://flake8.pycqa.org/) section, which is not yet supported in +[`pyproject.toml`](pyproject.toml). +We specify the line length for ruff in [`pyproject.toml`](pyproject.toml). and +for flake8 in [`setup.cfg`](setup.cfg).
### Changes required in `pyproject.toml` You have to change entries under the `[project]` section to match your project name, -description, author, license, etc. The `dependencies` section can list the dependencies +description, author, license, etc. The `dependencies` key can list the dependencies and is currently commented out. The dependencies could also be specified in via a `requirements.txt`, if you already have such a file. @@ -88,13 +95,13 @@ dynamic = ["version"] means that the version is loaded dynamically using the extension [setuptools_scm](https://setuptools-scm.readthedocs.io/) -we list under the `[build-system]` section in `pyproject.toml`. +we list under the `[build-system]` section in [`pyproject.toml`](pyproject.toml). This is done to avoid having to manually update the version and integrate with automatic versioning through releases on GitHub. It also ensures that each commit has a unique version number, which is useful for attributing errors to specific non-released versions. The dynamic version is picked up in the `__version__` variable in the `__init__.py` file of the package, which is located in the -`src/python_package` directory. +[`src/python_package`](src/python_package) directory. ```toml [build-system] @@ -136,8 +143,9 @@ name = "my_package" ``` Strictly speaking you can give different names in both places, but this will only confuse -potential users. Think of `scikit-learn` for an example of a package that has a different -name in the [`pyproject.toml`](pyproject.toml) file and the source code directory name. +potential users. Think of `scikit-learn` for an example of a package that uses a different +name in the [`pyproject.toml`](pyproject.toml) file and the source code directory name, +leading to the `sklearn` package name when imported. ## Documentation @@ -146,7 +154,7 @@ which is common for Python documentation. It relies additionally on several exte enabling the use of `markdown` and `jupyter` notebooks. The documentation is located in the [`docs`](docs) directory. Sphinx is configured via -the [`conf.py`](docs/conf.py) file, where you can specify the extension you want. +the [`conf.py`](docs/conf.py) file, where you can specify the extension you want: ```python # in docs/conf.py @@ -180,6 +188,8 @@ docs = [ ] ``` +### Required changes in `conf.py` + The required changes in [`conf.py`](docs/conf.py) are at the following places: ```python @@ -212,6 +222,8 @@ if os.environ.get("READTHEDOCS") == "True": The last block is for Read The Docs to be able to generate the API documentation of your package on the fly. See the Read The Docs section below for more details. +### Theme, autodoc and intersphinx + We build the documentation based on the template [sphinx_book_theme](https://sphinx-book-theme.readthedocs.io), which is set in the [`conf.py`](docs/conf.py) file and parts of our docs requirements in @@ -226,8 +238,10 @@ html_theme = "sphinx_book_theme" > [sphinx-themes.org](https://sphinx-themes.org/) The API of the Python package in the `src` directory is automatically included -in the documentation using the `autodoc` extension. The API documentation can be -augumented with hightlights from other types from projects using `intersphinx`: +in the documentation using the +[`autodoc` extension](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html). +The API documentation can be augmented with highlights from other types from projects +using `intersphinx`: ```python # Intersphinx options @@ -242,9 +256,11 @@ intersphinx_mapping = { Here we only add the core Python documentation, but you can add more projects like `pandas`, `scikit-learn`, or `matplotlib` to the mapping. +### Building the documentation locally (with integration tests) + To build the documentation locally, you can follow the instructions in the [`docs/README.md`](docs/README.md), which you should also update with your name changes. -In short, you can run the following commands in the `docs` directory: +In short, you can run the following commands in the [`docs`](docs ) directory: ```bash # in root of the project @@ -255,8 +271,8 @@ sphinx-build -n -W --keep-going -b html ./ ./_build/ ``` this will create a `reference` directory with the API documentation of the Python package -`python_package`, a `jupyter_execute` for the tutorial in `docs/tutorial` and a -`_build` directory with an HTML version of the documentation. You can open the +`python_package`, a `jupyter_execute` for the tutorial in [`docs/tutorial`](docs/tutorial) + and a `_build` directory with an HTML version of the documentation. You can open the `_build/index.html` file in your browser to view the documentation built locally. The tutorial related configuration in `conf.py` is the following, specifying that @@ -297,9 +313,9 @@ format to `py:percent` and automatically allows syncing of new notebooks. ### Read The Docs To build the documentation on Read The Docs, you need to create a file called -`.readthedocs.yaml`, which is located in the root of the project and specifies which -dependencies are needed. The core is the following specifying where the `conf.py` file -is and from where to install the required dependencies: +[`.readthedocs.yaml`](.readthedocs.yaml), which is located in the root of the project and +specifies which dependencies are needed. The core is the following specifying where the +[`conf.py`](docs/conf.py) file is and from where to install the required dependencies: ```yaml # Build documentation in the "docs/" directory with Sphinx @@ -317,8 +333,9 @@ python: You will need to manually register your project repository on [Read The Docs](https://readthedocs.org/) in order that it can build the documentation by the service. I recommend to activate builds for Pull Requests, so that -the documentation is built for each PR and you can see if the documentation gradually -breaking and your work advancing. See their documentation +the documentation is built for each PR and you can see if the documentation is gradually +breaking, i.e. your integration test using the notebooks in +[`docs/tutorial`](docs/tutorial) fail. See their documentation [on adding a project](https://docs.readthedocs.com/platform/stable/intro/add-project.html) for instructions. @@ -335,7 +352,7 @@ linter `ruff`: dev = ["black[jupyter]", "ruff", "pytest"] ``` -Instead of running these tools manually, typing: +Instead of running these tools manually, typing ```bash black . @@ -343,7 +360,7 @@ ruff check . pytest tests ``` -Read the next section to see how this is automated using `GitHub Actions`. +read the next section to see how this is automated using `GitHub Actions`. ## GitHub Actions @@ -414,7 +431,7 @@ jobs: run: python -m pytest tests ``` -This files also allows to create `PyPI` releases automatically if you register your +This workflow also allows to create `PyPI` releases automatically if you register your project on `PyPI` (or `TestPyPI` for testing first) and create a GitHub release: ```yaml @@ -441,9 +458,10 @@ project on `PyPI` (or `TestPyPI` for testing first) and create a GitHub release: repository-url: https://test.pypi.org/legacy/ ``` -To setup the `gh-action-pypi-publish` action, you need to register the repository -on `PyPI` or `TestPyPI`, which allows PyPI and GitHub to communicate securely. -See the instructions on +To setup the [`gh-action-pypi-publish`](https://github.com/pypa/gh-action-pypi-publish) +action, you need to register the repository +on [PyPI](https://pypi.org/) or [`TestPyPI`](https://test.pypi.org/), which allows PyPI +and GitHub to communicate securely. See the instructions on [packaging.python.org](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/). > The wheels are not built by default, but you can be necessary for packages which need @@ -468,13 +486,11 @@ python_package ├── tests │ ├── __init__.py │ └── test_mockup.py # files and test_function need to start with test_ to be recognized by pytest -├── LICENSE -├── MANIFEST.in -├── pyproject.toml -├── pytest.ini -├── README.md -├── setup.cfg # only used to set flake8 line length -└── setup.py +├── LICENSE # License file specifying usage terms +├── MANIFEST.in # non-python files to include into the build package +├── pyproject.toml # python package metadata, dependencies and configurations (incl. build tools) +├── pytest.ini # pytest configuration +├── README.md # README which is rendered on GitHub (or other hosting services) +└── setup.cfg # old python configuration file, only used to set flake8 line length +└── setup.py # artefact for backward compatibility, do not change ``` - - From e09d61082362768751504d5ba5dceb0825f9e45d Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Fri, 27 Jun 2025 10:59:55 +0200 Subject: [PATCH 11/22] :art: make name generic and document it. Choose a Licence hint --- LICENSE | 2 +- README.md | 14 +++++++++++--- developing.md | 7 +++---- docs/conf.py | 4 ++-- pyproject.toml | 3 +-- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/LICENSE b/LICENSE index db82110..88a548a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023: Jakob Nybo Nissen. +Copyright (c) 2025: First Last. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 59bcc49..a248d35 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Example Python package All design principles are explained in the [developing.md](developing.md) file. +The Python package template was created by Jakob Nybo Nissen and Henry Webel. ## How to use @@ -12,9 +13,13 @@ You will need to find and replace occurences of - `python_package` -> `your_package_name` - also the folder `src/python_package` - `RasmussenLab` -> `GitHub_user_name` (or `organization`) - with the name of your package and GitHub user name (or organization). +- look for `First Last` to see where to replace with your name +- choose a license, see [GitHub documentation](https://docs.github.com/en/repositories/creating-and-managing-repositories/licensing-a-repository) + and [Creative Commons](https://creativecommons.org/chooser/). + Replace [`LICENSE`](LICENSE) file with the license you choose. + ## Development environment Install package so that new code is picked up in a restared python interpreter: @@ -25,6 +30,8 @@ pip install -e ".[dev]" ## Basic usage +> works using this template + ```python from python_package import hello_world print (python_package.__version__) @@ -34,8 +41,9 @@ print(hello_world(4)) ## Readthedocs The documentation can be build using readthedocs automatically. See -[project on Readthedocs](https://readthedocs.org/projects/rasmussenlab-python-package/) for the project. A new project needs -to be registered. +[project on Readthedocs](https://readthedocs.org/projects/rasmussenlab-python-package/) +for the project based on this template. A new project needs +to [be registered on ReadTheDocs](https://docs.readthedocs.com/platform/stable/intro/add-project.html). - make sure to enable build from PRs in the settings (advanded settings) - checkout configuration file: [`.readthedocs.yaml`](.readthedocs.yaml) diff --git a/developing.md b/developing.md index 8ea7ad9..86dad05 100644 --- a/developing.md +++ b/developing.md @@ -63,8 +63,7 @@ and is currently commented out. The dependencies could also be specified in via # ref: https://setuptools.pypa.io/en/stable/userguide/pyproject_config.html [project] authors = [ - { name = "Jakob Nybo Nissen", email = "jakobnybonissen@gmail.com" }, - { name = "Henry Webel", email = "henry.webel@sund.ku.dk" }, + { name = "First Last", email = "first.last@gmail.com" }, ] description = "A small example package" name = "python_package" @@ -196,8 +195,8 @@ The required changes in [`conf.py`](docs/conf.py) are at the following places: # in docs/conf.py project = "python_package" -copyright = "2025, Jakob Nybo Nissen, Henry Webel" -author = "Jakob Nybo Nissen, Henry Webel" +copyright = "2025, First Last" +author = "First Last" PACKAGE_VERSION = metadata.version("python_package") # ... diff --git a/docs/conf.py b/docs/conf.py index b54becf..4851ebe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,8 +16,8 @@ # -- Project information ----------------------------------------------------- project = "python_package" -copyright = "2025, Jakob Nybo Nissen, Henry Webel" -author = "Jakob Nybo Nissen, Henry Webel" +copyright = "2025, First Last" +author = "First Last" PACKAGE_VERSION = metadata.version("python_package") version = PACKAGE_VERSION release = PACKAGE_VERSION diff --git a/pyproject.toml b/pyproject.toml index a247dcf..11f7332 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,7 @@ # ref: https://setuptools.pypa.io/en/stable/userguide/pyproject_config.html [project] authors = [ - { name = "Jakob Nybo Nissen", email = "jakobnybonissen@gmail.com" }, - { name = "Henry Webel", email = "henry.webel@sund.ku.dk" }, + { name = "First Last", email = "first.last@gmail.com" }, ] description = "A small example package" name = "python_package" From 3ba8502a74966e07666794100d18d4fd9e3a1cba Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Fri, 27 Jun 2025 11:32:03 +0200 Subject: [PATCH 12/22] :art: add design document to Sphinx documentation --- developing.md | 5 +++-- docs/developing.md | 5 +++++ docs/index.md | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 docs/developing.md diff --git a/developing.md b/developing.md index 86dad05..0015f28 100644 --- a/developing.md +++ b/developing.md @@ -1,4 +1,4 @@ -# Python package template +# Design descriptions and details for the Python package template > Author: Henry Webel @@ -32,7 +32,8 @@ python_package ## Core packaging files -We will first look at [`pyproject.toml`] and it's relation to the [`src`](src) directory. The +We will first look at [`pyproject.toml`](pyproject.toml) and it's relation to the +[`src`](src) directory. The [`project.toml`](pyproject.toml) file is the main configuration file for the Python package and is used to specify the package metadata, dependencies, build tools and configurations. The [`src`](src) folder stores the actual source code of the package, where the package itself is diff --git a/docs/developing.md b/docs/developing.md new file mode 100644 index 0000000..88bf941 --- /dev/null +++ b/docs/developing.md @@ -0,0 +1,5 @@ +```{include} ../developing.md +:start-line: 0 +:relative-docs: www.rasmussenlab.com/python_package/docs +:relative-images: +``` diff --git a/docs/index.md b/docs/index.md index 3d68bf7..e9c9db3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,6 +30,7 @@ reference/python_package :hidden: true README +developing ``` ## Indices and tables From a01647c88ee7aeddc23f7c59bc7d16545246c2e2 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Fri, 27 Jun 2025 13:09:37 +0200 Subject: [PATCH 13/22] :art: add some more common error checking - set line-length to black default of 88 characters --- .github/workflows/cicd.yml | 2 +- pyproject.toml | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 81ffcf3..3d485ce 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -34,7 +34,7 @@ jobs: - name: Lint with ruff run: | # stop the build if there are Python syntax errors or undefined names - ruff check . + ruff check src test: name: Test runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index 11f7332..2952fa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,9 +46,17 @@ dev = ["black[jupyter]", "ruff", "pytest"] # Configure the Ruff linter: Ignore error number 501 [tool.ruff] # https://docs.astral.sh/ruff/rules/#flake8-bandit-s -lint.ignore = ["E501"] # Ignore line length errors -# Allow lines to be as long as: -line-length = 90 +# lint.ignore = ["E501"] # Ignore line length errors +# Allow lines to be as long as (default is 88 in black) +# This is the same as the default in black, but we set it explicitly here. +line-length = 88 + +[tool.ruff.lint] +# https://docs.astral.sh/ruff/tutorial/#rule-selection +# 1. Enable flake8-bugbear (`B`) rules +# 2. Enable pycodestyle (`E`) errors and (`W`) warnings +# 3. Pyflakes (`F`) errors +extend-select = ["E", "W", 'F', 'B'] [build-system] build-backend = "setuptools.build_meta" @@ -60,3 +68,6 @@ requires = ["setuptools>=64", "setuptools_scm>=8"] [tool.isort] profile = "black" + +[tool.black] +line-length = 88 From a5a78b63eb72a43f1991d0bfd0a679fae81e36d2 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Fri, 27 Jun 2025 13:19:18 +0200 Subject: [PATCH 14/22] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- developing.md | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/developing.md b/developing.md index 0015f28..228a31e 100644 --- a/developing.md +++ b/developing.md @@ -32,7 +32,7 @@ python_package ## Core packaging files -We will first look at [`pyproject.toml`](pyproject.toml) and it's relation to the +We will first look at [`pyproject.toml`](pyproject.toml) and its relation to the [`src`](src) directory. The [`project.toml`](pyproject.toml) file is the main configuration file for the Python package and is used to specify the package metadata, dependencies, build tools and configurations. diff --git a/pyproject.toml b/pyproject.toml index 2952fa5..e343459 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ line-length = 88 # 1. Enable flake8-bugbear (`B`) rules # 2. Enable pycodestyle (`E`) errors and (`W`) warnings # 3. Pyflakes (`F`) errors -extend-select = ["E", "W", 'F', 'B'] +extend-select = ["E", "W", "F", "B"] [build-system] build-backend = "setuptools.build_meta" From f04d90ff576dcf7774f9adeca26d5bd6873bb040 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Fri, 27 Jun 2025 13:23:01 +0200 Subject: [PATCH 15/22] :bug: correct python pkg configuration file --- developing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developing.md b/developing.md index 228a31e..79db9ca 100644 --- a/developing.md +++ b/developing.md @@ -34,7 +34,7 @@ python_package We will first look at [`pyproject.toml`](pyproject.toml) and its relation to the [`src`](src) directory. The -[`project.toml`](pyproject.toml) file is the main configuration file for the Python package +[`pyproject.toml`](pyproject.toml) file is the main configuration file for the Python package and is used to specify the package metadata, dependencies, build tools and configurations. The [`src`](src) folder stores the actual source code of the package, where the package itself is the subdirectories of the [`src`](src) directory. The (e.g. `src/python_package`). From 32f5bacf9636034e8de1819aede46146a0ad1151 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Sun, 29 Jun 2025 22:51:50 +0200 Subject: [PATCH 16/22] :bug: add missing changes for requirments.txt As noted by Angel using requirements.txt needed one additional change: - add dependencies to the dynamic field Add hint to section with dependencies --- pyproject.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e343459..dce59ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,9 +7,11 @@ description = "A small example package" name = "python_package" # This means: Load the version from the package itself. # See the section below: [tools.setuptools.dynamic] -dynamic = ["version"] +dynamic = ["version", +#"dependencies", # add if using requirements.txt +] readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.9" # test all higher Python versions # These are keywords classifiers = [ "Programming Language :: Python :: 3", @@ -20,6 +22,7 @@ classifiers = [ # dependencies = ["numpy", "pandas", "scipy", "matplotlib", "seaborn"] # use requirements.txt instead of pyproject.toml for dependencies # https://stackoverflow.com/a/73600610/9684872 +# ! uncomment also dependencies in the dynamic section above # [tool.setuptools.dynamic] # dependencies = {file = ["requirements.txt"]} From 1bf4ad0536fc2aaf2591618e61f50a703db8f26f Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Mon, 30 Jun 2025 16:20:36 +0200 Subject: [PATCH 17/22] :memo: Add hint to GitHub Releases and Source distribution testing --- developing.md | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/developing.md b/developing.md index 79db9ca..cdbbd4c 100644 --- a/developing.md +++ b/developing.md @@ -464,8 +464,46 @@ on [PyPI](https://pypi.org/) or [`TestPyPI`](https://test.pypi.org/), which allo and GitHub to communicate securely. See the instructions on [packaging.python.org](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/). -> The wheels are not built by default, but you can be necessary for packages which need -> to be partly compiled, e.g. if you use `Cython`, `numpy` C extensions or Rust extensions. +You then trigger new releases to PyPI by creating a new GitHub release, which will +automatically trigger the `publish` job in the workflow as it needs you to set a tag. +Have a look at [VueGen Releases]( https://github.com/Multiomics-Analytics-Group/vuegen/releases) +for an example. The release notes are automatically generated using the PR titles, +see GitHub's +[docs](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes). + +
+Wheels and testing builds +The wheels are not built by default, but you can be necessary for packages which need +to be partly compiled, e.g. if you use `Cython`, `numpy` C extensions or Rust extensions. + +Also additionally you could use the artifact from the `build_source_dist` job +to test the build of the source distribution. This is useful to ensure that a package +with non-Python files (e.g. data files) is built correctly and that the package +can be installed correctly. You should probably best test this in as much isolation as +you can, e.g. by not pulling the repository using `actions/checkout@v4`. + +```yaml + test_sdist: + name: Install built source distribution + needs: build_source_dist + runs-on: ubuntu-latest + steps: + # - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: artifact + path: ./dist + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install built sdist + run: | + pip install ./dist/*.tar.gz + # ... some checks +``` +
+ + ## Full project structure From 4bedc9561a4a5e77933baf7716efaf4aa08201d2 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Tue, 1 Jul 2025 11:46:29 +0200 Subject: [PATCH 18/22] :art: follow Pasquale's advices (.gitignore + unnecessary config) - add more to .gitignore and link template - remove line-length configuration as it is already on the default 88 of black formatter --- .gitignore | 27 +++++++++++++++++++++++++++ developing.md | 21 ++++++++++++++------- pyproject.toml | 7 +------ setup.cfg | 4 ---- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 84675af..1604d52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# find more examples here: +# https://github.com/github/gitignore/blob/main/Python.gitignore # Binaries (object files) produced by a compiler *.so *.o @@ -27,3 +29,28 @@ _templates # VSCode may create a config file with this name **.vscode + +# Ruff stuff: +.ruff_cache/ + +# Environments +.env +.envrc +.venv +env/ +venv/ + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ diff --git a/developing.md b/developing.md index cdbbd4c..84d4245 100644 --- a/developing.md +++ b/developing.md @@ -26,7 +26,7 @@ python_package ├── pyproject.toml # python package metadata, dependencies and configurations (incl. build tools) ├── pytest.ini # pytest configuration ├── README.md # README which is rendered on GitHub (or other hosting services) -└── setup.cfg # old python configuration file, only used to set flake8 line length +└── setup.cfg # old python configuration file, empty └── setup.py # artefact for backward compatibility, do not change ``` @@ -45,11 +45,18 @@ the subdirectories of the [`src`](src) directory. The (e.g. `src/python_package The [`setup.py`](setup.py) file is an artefact for backward compatibility and should not be changed. Everything that used to be in [`setup.py`](setup.py) or [`setup.cfg`](setup.cfg) is now largely in [`pyproject.toml`](pyproject.toml). -The notable exception is specifying the desired maximum line length in `setup.cfg` via -the [`flake8`](https://flake8.pycqa.org/) section, which is not yet supported in -[`pyproject.toml`](pyproject.toml). -We specify the line length for ruff in [`pyproject.toml`](pyproject.toml). and -for flake8 in [`setup.cfg`](setup.cfg). +The notable exception would be the desired maximum line length in `setup.cfg` for +the tool [`flake8`](https://flake8.pycqa.org/), which does not yet supported +[`pyproject.toml`](pyproject.toml) configuration. As we use `ruff` as linter, +we left it empty, but in case you want to use `flake8`, you can add: + +```INI +; setup.cfg +[flake8] +exclude = docs +max-line-length = 88 +aggressive = 2 +``` @@ -529,6 +536,6 @@ python_package ├── pyproject.toml # python package metadata, dependencies and configurations (incl. build tools) ├── pytest.ini # pytest configuration ├── README.md # README which is rendered on GitHub (or other hosting services) -└── setup.cfg # old python configuration file, only used to set flake8 line length +└── setup.cfg # old python configuration file, empty └── setup.py # artefact for backward compatibility, do not change ``` diff --git a/pyproject.toml b/pyproject.toml index dce59ee..091e303 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ description = "A small example package" name = "python_package" # This means: Load the version from the package itself. # See the section below: [tools.setuptools.dynamic] -dynamic = ["version", +dynamic = ["version", # version is loaded from the package #"dependencies", # add if using requirements.txt ] readme = "README.md" @@ -51,8 +51,6 @@ dev = ["black[jupyter]", "ruff", "pytest"] # https://docs.astral.sh/ruff/rules/#flake8-bandit-s # lint.ignore = ["E501"] # Ignore line length errors # Allow lines to be as long as (default is 88 in black) -# This is the same as the default in black, but we set it explicitly here. -line-length = 88 [tool.ruff.lint] # https://docs.astral.sh/ruff/tutorial/#rule-selection @@ -71,6 +69,3 @@ requires = ["setuptools>=64", "setuptools_scm>=8"] [tool.isort] profile = "black" - -[tool.black] -line-length = 88 diff --git a/setup.cfg b/setup.cfg index 12062ee..e69de29 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +0,0 @@ -[flake8] -exclude = docs -max-line-length = 90 -aggressive = 2 From 2467b6134abf8359331d54da12d21d32a30116de Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Tue, 1 Jul 2025 11:47:14 +0200 Subject: [PATCH 19/22] :memo: add sebastians hints on other ressources --- developing.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/developing.md b/developing.md index 84d4245..ae2c8b0 100644 --- a/developing.md +++ b/developing.md @@ -7,10 +7,18 @@ has an excellent tutorial on how to package a Python project. I read and used in that website to help create the template which is available on GitHub at [https://github.com/RasmussenLab/python_package](https://github.com/RasmussenLab/python_package) and I want to give here an overview specifically to some details regarding this template. -Some are overlapping with the [packaging.python.org](https://packaging.python.org/en/latest/tutorials/packaging-projects/) +Some are overlapping with the +[packaging.python.org](https://packaging.python.org/en/latest/tutorials/packaging-projects/) tutorial, but as always we decided for a certain set of tools, conventions and complexity which needs some explanation. +Here a brief overview of external resources you can also look at: + +- [packaging.python.org](https://packaging.python.org/en/latest/tutorials/packaging-projects/) +- [setuptools documentation](https://setuptools.pypa.io/en/latest/userguide/index.html) +- [learn.scientific-python.org](https://learn.scientific-python.org/development/) +- [Py-Pkgs](https://py-pkgs.org/) + ## Project structure First an overview of the main folder structure. See line comments for details on what From afbbd704129e1ecf16991d6ed4a50060162a63e8 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Tue, 1 Jul 2025 11:48:19 +0200 Subject: [PATCH 20/22] :art: add license key and remove from classifiers as suggested by Sebastian --- developing.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/developing.md b/developing.md index ae2c8b0..8033544 100644 --- a/developing.md +++ b/developing.md @@ -71,9 +71,13 @@ aggressive = 2 ### Changes required in `pyproject.toml` You have to change entries under the `[project]` section to match your project name, -description, author, license, etc. The `dependencies` key can list the dependencies -and is currently commented out. The dependencies could also be specified in via a -`requirements.txt`, if you already have such a file. +description, author, license, etc. Make sure to pick a license that works for you, e.g. +using [choosealicense.com](https://choosealicense.com/). Also update the `LICENSE` file +accordingly. + +The `dependencies` key can +list the dependencies and is currently commented out. The dependencies could also be +specified in via a `requirements.txt`, if you already have such a file. ```toml # ref: https://setuptools.pypa.io/en/stable/userguide/pyproject_config.html @@ -85,15 +89,17 @@ description = "A small example package" name = "python_package" # This means: Load the version from the package itself. # See the section below: [tools.setuptools.dynamic] -dynamic = ["version"] # version is loaded from the package +dynamic = ["version", # version is loaded from the package +#"dependencies", # add if using requirements.txt +] readme = "README.md" requires-python = ">=3.9" # These are keywords classifiers = [ "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] +license = "MIT" # https://choosealicense.com/ # # add dependencies here: (use one of the two) # dependencies = ["numpy", "pandas", "scipy", "matplotlib", "seaborn"] # use requirements.txt instead of pyproject.toml for dependencies From 15771b9ac7b0372c8aa1da0d4cb02f64439a6064 Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Tue, 1 Jul 2025 11:48:51 +0200 Subject: [PATCH 21/22] :memo: docstring and src layout hints (noted by sebastian) --- developing.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/developing.md b/developing.md index 8033544..d5424c9 100644 --- a/developing.md +++ b/developing.md @@ -145,8 +145,12 @@ Please also update the project URL to your project: ## Source directory layout of the package The source code of the package is located in the `src` directory, to have a project -independent folder to look for the source code. It also allows to have multiple packages -in the same project, although this is not our main use case. +independent folder to look for the source code recognized by most tools you would need +to build a package +(read on [packagin namespace packages](https://packaging.python.org/en/latest/guides/packaging-namespace-packages/)). +It also allows to have multiple subpackages or modules +in the same project under the `python_package` package (see example +[here](https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#src-layout)). ```bash ├── src @@ -261,6 +265,8 @@ html_theme = "sphinx_book_theme" The API of the Python package in the `src` directory is automatically included in the documentation using the [`autodoc` extension](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html). +We use per default the `numpydoc` style for docstrings, see the format +[here](https://numpydoc.readthedocs.io/en/stable/format.html). The API documentation can be augmented with highlights from other types from projects using `intersphinx`: From 3e6b80545d5b337e7fcdc3d15676dc996d683bdf Mon Sep 17 00:00:00 2001 From: Henry Webel Date: Tue, 1 Jul 2025 11:49:15 +0200 Subject: [PATCH 22/22] :bug: add missing key to pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 091e303..7a59c2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,9 +15,9 @@ requires-python = ">=3.9" # test all higher Python versions # These are keywords classifiers = [ "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] +license = "MIT" # https://choosealicense.com/ # # add dependencies here: (use one of the two) # dependencies = ["numpy", "pandas", "scipy", "matplotlib", "seaborn"] # use requirements.txt instead of pyproject.toml for dependencies