From 6e99d6172d8c6e36c5159c5d1b17d1096e6d28a3 Mon Sep 17 00:00:00 2001 From: Jeremy Paige Date: Wed, 10 Apr 2024 11:39:49 -0700 Subject: [PATCH 1/4] PoC: literalinclude directives --- examples/pure-hatch/pyproject.toml | 38 +++++++ examples/pure-setuptools/pyproject.toml | 24 +++++ .../pyproject-toml-python-package-metadata.md | 101 ++++-------------- 3 files changed, 84 insertions(+), 79 deletions(-) create mode 100644 examples/pure-hatch/pyproject.toml create mode 100644 examples/pure-setuptools/pyproject.toml diff --git a/examples/pure-hatch/pyproject.toml b/examples/pure-hatch/pyproject.toml new file mode 100644 index 00000000..77e11179 --- /dev/null +++ b/examples/pure-hatch/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "examplePy" +authors = [ + {name = "Some Maintainer", email = "some-email@pyopensci.org"}, +] +maintainers = [ + {name = "All the contributors"}, +] +description = "An example Python package used to support Python packaging tutorials" +keywords = ["pyOpenSci", "python packaging"] +readme = "README.md" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", +] +dependencies = [ + "dependency-package-name-1", + "dependency-package-name-2", +] + +[project.optional-dependencies] +tests = [ + "pytest", + "pytest-cov" +] +lint = [ + "black", + "flake8" +] +docs = [ + "sphinx", + "pydata-sphinx-theme" +] diff --git a/examples/pure-setuptools/pyproject.toml b/examples/pure-setuptools/pyproject.toml new file mode 100644 index 00000000..e8d6d27f --- /dev/null +++ b/examples/pure-setuptools/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[project] +name = "examplePy" +authors = [ + {name = "Some Maintainer", email = "some-email@pyopensci.org"}, +] +maintainers = [ + {name = "All the contributors"}, +] +description = "An example Python package used to support Python packaging tutorials" +keywords = ["pyOpenSci", "python packaging"] +readme = "README.md" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", +] +dependencies = [ + "dependency-package-name-1", + "dependency-package-name-2", +] diff --git a/package-structure-code/pyproject-toml-python-package-metadata.md b/package-structure-code/pyproject-toml-python-package-metadata.md index 6a65be7f..4bd186f6 100644 --- a/package-structure-code/pyproject-toml-python-package-metadata.md +++ b/package-structure-code/pyproject-toml-python-package-metadata.md @@ -88,14 +88,13 @@ Below that table identifier are key/value pairs that support configuration for that particular table. - Below `[build-system]` is considered a table in the toml language. -- Within the build-system table below requires = is a key. -- The associated value for requires is an array containing the value "hatchling". +- Within the `build-system` table below `requires =` is a key. +- The associated value for `requires` is an array containing the value `"hatchling"`. -```toml -[build-system] # <- this is a table -requires = ["hatchling"] # requires = is a key and "hatchling" is a value contained within an array specified by square brackets []. - -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:lines: 1-2 +::: ### How the pyproject.toml is used when you build a package @@ -160,11 +159,10 @@ what dependencies your package requires. - **Authors:** these are the original authors of the package. Sometimes the authors are different from the maintainers. Other times they might be the same. - **Maintainers:** you can choose to populate this or not. You can populate this using a list with a sub element for each author or maintainer name, email -```toml -authors = [ - {name = "A. Random Developer", email = "author@example.com" } -] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:lines: 7-9 +::: - **dependencies:** dependencies are optional but we strongly suggest you include them in your pyproject.toml. Dependencies will be installed by pip when your project is installed creating a better user-experience. @@ -194,21 +192,10 @@ To add dependencies to your build, add a `[project.optional-dependencies]` table Then specify dependency groups as follows: -``` -[project.optional-dependencies] -tests = [ - "pytest, - "pytest-cov" -] -lint = [ - "black", - "flake8" -] -docs = [ - "sphinx", - "pydata-sphinx-theme -] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:lines: 26-38 +::: Following the above example, you install dependencies like this: @@ -232,32 +219,10 @@ You can also setup sets of recursive dependencies. [See this blog post for more. Below is an example build configuration for a Python project. This example package setup uses **hatchling** to build the [package's sdist and wheels](python-package-distribution-files-sdist-wheel). -```toml -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "examplePy" -authors = [ - {name = "Some Maintainer", email = "some-email@pyopensci.org"}, -] -maintainers = [ - {name = "All the contributors"}, -] -description = "An example Python package used to support Python packaging tutorials" -keywords = ["pyOpenSci", "python packaging"] -readme = "README.md" -classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", -] -dependencies = [ - "dependency-package-name-1", - "dependency-package-name-2", -] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:lines: 1-24 +::: Notice that dependencies are specified in this file. @@ -278,32 +243,10 @@ of values. It has two keys that specify the build backend API and containing pac 1. `requires =` 1. `build-back-end =` -``` -[build-system] -requires = ["setuptools>=61"] -build-backend = "setuptools.build_meta" - -[project] -name = "examplePy" -authors = [ - {name = "Some Maintainer", email = "some-email@pyopensci.org"}, -] -maintainers = [ - {name = "All the contributors"}, -] -description = "An example Python package used to support Python packaging tutorials" -keywords = ["pyOpenSci", "python packaging"] -readme = "README.md" -classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", -] -dependencies = [ - "dependency-package-name-1", - "dependency-package-name-2", -] -``` +:::{literalinclude} ../examples/pure-setuptools/pyproject.toml +:language: toml +:lines: 1-24 +::: ```{note} [Click here to read about our packaging build tools including PDM, setuptools, Poetry and Hatch.](/package-structure-code/python-package-build-tools) From 90ca18957f5f37a5a729fee9c856fdfc18ca63c4 Mon Sep 17 00:00:00 2001 From: Jeremiah Paige Date: Fri, 10 May 2024 09:39:11 -0700 Subject: [PATCH 2/4] example code contributing --- CONTRIBUTING.md | 111 +++++++++++++++++- .../pyproject-toml-python-package-metadata.md | 11 +- 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf26b027..d2af815a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ This is a community resource. We welcome contributions in the form of issues and * If you find a typo, feel free to [submit a pull request](https://github.com/pyOpenSci/python-package-guide/pulls) to modify the text directly. Or, if you are less comfortable with pull requests, feel free to open an issue. * If you want to see a larger change to the content of the guide book, please submit an issue first! -## How this guide structured +## How this guide is structured Most of this repository is structured for **Sphinx**, a documentation engine built in `Python`. We are using the Sphinx Book Theme and the `myST` syntax to create each page in this book. @@ -31,18 +31,121 @@ To do so, follow these steps: 1. Install `nox` ``` - pip install nox + python -m pip install nox ``` 2. Build the documentation: ``` - nox -s docs + python -m nox -s docs ``` This should create a local environment in a `.nox` folder, build the documentation (as specified in the `noxfile.py` configuration), and the output will be in `_build/html`. +The site can then be viewed locally by opening the top level `index.html` in your web browser. The exact location of this file will depend on you system, but the output of the following command could be copied into an address bar + +``` +echo "file://$(pwd)/_build/html/index.html" +``` To build live documentation that updates when you update local files, run the following command: ``` -nox -s docs-live +python -m nox -s docs-live +``` + +When build like this, the output will tell you a localhost address where the site can be viewed, generally http://127.0.0.1:8000. + +## Code examples + +This guide uses the [literalinclude Sphinx directive](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-literalinclude) +whenever possible to keep code and prose separate. Code for use in the documentation is kept in the `examples/` folder. + +### Referencing code in documentation + +If an example is present elsewhere in the documentation that you want to use, you can copy the `literalinclude` +directive verbatim and the examples will stay in sync. + +If you already see code in the examples folder that you can use for new documentation, a new `literalinclude` can be +made to extract it into the site. Only a relative path to the code is required for a working `literalinclude`, but you +should in almost all cases also provide a `:language:` and `:lines:`. The former makes code examples prettier, and the +later can protect your example from future modifications to the code. + +**Pro tip**: As an alternative to `:lines:` there are also the `:start-after:`, `:start-at:`, `:end-before:`, and +`:end-at:` options. And if the example code is Python, `:pyobject:` can be an even more future-proof way to keep the +same documentation content even through code refactors. + +If you need example code that doesn't yet exist in `examples/` see (#creating-code-for-documentation). + +### Creating code for documentation + +Whenever you come across a place that could benefit from a code block, instead of writing it in-line with a code fence +(`` ``` `` blocked text) you can write it as a file in its own format. Your example may even already exist; see +(#referencing-code-in-documentation). + +If you need a net new example and it doesn't fit into any existing example files, you can create a new file and +reference it in a `literalinclude`. If it makes sense for that file to live within one of the existing example +projects please add it there; otherwise create a new folder in the examples. + +If an existing example is incomplete or a new example makes sense to be added to an existing file, go ahead and add it, +but take care to not break the rest of the guide. Whenever possible, extend the example rather that rewrite it. So for +instance, add new functions to the end of the file, new methods after all existing ones in a class. + +Example code is checked for correctness, so adding a new example may require adding additional tests for coverage, and +will require fixing any failing tests. + +***WARNING***: great care should be taken when modifying existing example code, especially any modification beyond +appending to the end of the file. All code examples are (potentially) shared examples. This makes for more consistent +examples in the guide but can mean action-at-a-distance when modifying the examples for one particular use case. +If you find yourself modifying existing examples try running this command and then checking those pages in a new build. +```bash +grep -lr '\.\./examples/path/to/modified\.py' documentation/ +``` + +Example: + +Instead of writing example code in markdown like this + +````md +Here is an example Python function: + +```python +def is_empty(x): + return not bool(len(x)) +``` +```` + +The python can be extracted into a `.py` file +```python +def is_empty(x): + return not bool(len(x)) +``` + +````md +Here is an example Python function: + +:::{literalinclude} ../examples/contributing_example.py +:language: python +:lines: 1-2 +```` + +As another example, if you only need to show part of a `pyproject.toml`, we already have complete project definitions, +you need only to find the relevant part. + +Instead of writing this +````md +Classifiers are just a list of plain strings +```toml +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", +] +``` +```` + +an example could be extracted from an existing toml file +```md +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: classifiers = [ +:end-at: ] ``` diff --git a/package-structure-code/pyproject-toml-python-package-metadata.md b/package-structure-code/pyproject-toml-python-package-metadata.md index 4bd186f6..aab669e8 100644 --- a/package-structure-code/pyproject-toml-python-package-metadata.md +++ b/package-structure-code/pyproject-toml-python-package-metadata.md @@ -93,7 +93,8 @@ support configuration for that particular table. :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml -:lines: 1-2 +:start-at: [build-system] +:end-at: requires = [ ::: ### How the pyproject.toml is used when you build a package @@ -161,7 +162,8 @@ what dependencies your package requires. :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml -:lines: 7-9 +:start-at: authors = [ +:end-at: ] ::: - **dependencies:** dependencies are optional but we strongly suggest you include them in your pyproject.toml. Dependencies will be installed by pip when your project is installed creating a better user-experience. @@ -194,7 +196,7 @@ Then specify dependency groups as follows: :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml -:lines: 26-38 +:start-at: [project.optional-dependencies] ::: Following the above example, you install dependencies like this: @@ -221,7 +223,7 @@ package setup uses **hatchling** to build the [package's sdist and wheels](pytho :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml -:lines: 1-24 +:end-before: [project.optional-dependencies] ::: Notice that dependencies are specified in this file. @@ -245,7 +247,6 @@ of values. It has two keys that specify the build backend API and containing pac :::{literalinclude} ../examples/pure-setuptools/pyproject.toml :language: toml -:lines: 1-24 ::: ```{note} From b073199acd613f4675890f098751b30c39ccece5 Mon Sep 17 00:00:00 2001 From: Jeremiah Paige Date: Fri, 10 May 2024 09:51:58 -0700 Subject: [PATCH 3/4] contributing tweek --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2af815a..c70d87f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,13 +73,13 @@ later can protect your example from future modifications to the code. `:end-at:` options. And if the example code is Python, `:pyobject:` can be an even more future-proof way to keep the same documentation content even through code refactors. -If you need example code that doesn't yet exist in `examples/` see (#creating-code-for-documentation). +If you need example code that doesn't yet exist in `examples/` see [](#creating-code-for-documentation). ### Creating code for documentation Whenever you come across a place that could benefit from a code block, instead of writing it in-line with a code fence (`` ``` `` blocked text) you can write it as a file in its own format. Your example may even already exist; see -(#referencing-code-in-documentation). +[](#referencing-code-in-documentation). If you need a net new example and it doesn't fit into any existing example files, you can create a new file and reference it in a `literalinclude`. If it makes sense for that file to live within one of the existing example @@ -92,7 +92,7 @@ instance, add new functions to the end of the file, new methods after all existi Example code is checked for correctness, so adding a new example may require adding additional tests for coverage, and will require fixing any failing tests. -***WARNING***: great care should be taken when modifying existing example code, especially any modification beyond +***⚠️ WARNING***: great care should be taken when modifying existing example code, especially any modification beyond appending to the end of the file. All code examples are (potentially) shared examples. This makes for more consistent examples in the guide but can mean action-at-a-distance when modifying the examples for one particular use case. If you find yourself modifying existing examples try running this command and then checking those pages in a new build. From 9492fedccd72b549601bd31942e3da945a2a820c Mon Sep 17 00:00:00 2001 From: Jeremiah Paige Date: Mon, 20 May 2024 08:32:22 -0700 Subject: [PATCH 4/4] fixup: contributing links --- CONTRIBUTING.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c70d87f9..ebdbc94c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,17 +73,17 @@ later can protect your example from future modifications to the code. `:end-at:` options. And if the example code is Python, `:pyobject:` can be an even more future-proof way to keep the same documentation content even through code refactors. -If you need example code that doesn't yet exist in `examples/` see [](#creating-code-for-documentation). +If you need example code that doesn't yet exist in `examples/` [see creating code for documentation](#creating-code-for-documentation). ### Creating code for documentation Whenever you come across a place that could benefit from a code block, instead of writing it in-line with a code fence -(`` ``` `` blocked text) you can write it as a file in its own format. Your example may even already exist; see -[](#referencing-code-in-documentation). +(`` ``` `` blocked text) you can write it as a file in its own format. Your example may even already exist; [see referencing code in documentation +](#referencing-code-in-documentation). -If you need a net new example and it doesn't fit into any existing example files, you can create a new file and -reference it in a `literalinclude`. If it makes sense for that file to live within one of the existing example -projects please add it there; otherwise create a new folder in the examples. +If you want to add a new example that doesn't fit into any of the existing example files, you can create a new file and +reference it in a `literalinclude` block. If it makes sense for that file to live within one of the existing example +projects please add it there; otherwise create a new folder in the `examples` directory. If an existing example is incomplete or a new example makes sense to be added to an existing file, go ahead and add it, but take care to not break the rest of the guide. Whenever possible, extend the example rather that rewrite it. So for