Skip to content

Commit ffd7390

Browse files
authored
Merge branch 'main' into readthedocs_branch
2 parents 76f829c + 5b0e65b commit ffd7390

File tree

10 files changed

+234
-16
lines changed

10 files changed

+234
-16
lines changed

.github/workflows/cicd.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ jobs:
106106
publish:
107107
name: Publish package
108108
if: startsWith(github.ref, 'refs/tags')
109+
permissions:
110+
id-token: write
109111
needs:
110112
- format
111113
- lint
@@ -125,3 +127,4 @@ jobs:
125127
with:
126128
# remove repository key to set the default to pypi (not test.pypi.org)
127129
repository-url: https://test.pypi.org/legacy/
130+

CITATION.cff

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-citation-files
2+
cff-version: 1.2.0
3+
message: "If you use this software, please cite it as below."
4+
authors:
5+
- family-names: "Last"
6+
given-names: "First"
7+
orcid: "https://orcid.org/0000-0000-0000-0000"
8+
title: "Python Package Template repository"
9+
version: 0.0.1
10+
doi: 10.5281/zenodo.1234
11+
date-released: 2025-07-23
12+
url: "https://github.com/biosustain/python_package"

Contributing.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Contributing code
2+
3+
Install the code with development dependencies:
4+
5+
```bash
6+
pip install -e '.[dev]'
7+
```
8+
9+
## Format code and sort imports
10+
11+
```bash
12+
black .
13+
isort .
14+
```
15+
16+
## lint code
17+
18+
```bash
19+
ruff check .
20+
```
21+
22+
## Run tests
23+
24+
```bash
25+
pytest
26+
```
27+
28+
## Sync notebooks with jupytext
29+
30+
For easier diffs, you can use jupytext to sync notebooks in the `docs/tutorial` directory with the percent format.
31+
32+
```bash
33+
jupytext --sync docs/tutorial/*.ipynb
34+
```
35+
36+
This is configured in the [`.jupytext`](docs/tutorial/.jupytext) file in that directory.

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ see [GitHub documentation](https://docs.github.com/en/repositories/creating-and-
1111
You will need to find and replace occurences of
1212

1313
- `python_package` -> `your_package_name`
14-
- also the folder `src/python_package`
14+
- also the folder `src/python_package`
1515
- `RasmussenLab` -> `GitHub_user_name` (or `organization`)
16-
with the name of your package and GitHub user name (or organization).
16+
with the name of your package and GitHub user name (or organization).
1717

1818
- look for `First Last` to see where to replace with your name
1919
- choose a license, see [GitHub documentation](https://docs.github.com/en/repositories/creating-and-managing-repositories/licensing-a-repository)
2020
and [Creative Commons](https://creativecommons.org/chooser/).
2121
Replace [`LICENSE`](LICENSE) file with the license you choose.
22+
- Update the `CITATION.cff` file with your information.
2223

2324
## Development environment
2425

@@ -41,7 +42,7 @@ print(hello_world(4))
4142
## Readthedocs
4243

4344
The documentation can be build using readthedocs automatically. See
44-
[project on Readthedocs](https://readthedocs.org/projects/rasmussenlab-python-package/)
45+
[project on Readthedocs](https://readthedocs.org/projects/rasmussenlab-python-package/)
4546
for the project based on this template. A new project needs
4647
to [be registered on ReadTheDocs](https://docs.readthedocs.com/platform/stable/intro/add-project.html).
4748

docs/tutorial/.jupytext

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
# all notebooks in this directory are in the percent format
1+
# all notebooks in this directory are synced percent format when typing
2+
# (jupytext is a dev dependency)
3+
# jupytext --sync *.ipynb
4+
# or from root directory
5+
# jupytext --sync docs/api_examples/*.ipynb
26
formats = "ipynb,py:percent"

pyproject.toml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
# ref: https://setuptools.pypa.io/en/stable/userguide/pyproject_config.html
22
[project]
3-
authors = [
4-
{ name = "First Last", email = "first.last@gmail.com" },
5-
]
3+
authors = [{ name = "First Last", email = "first.last@gmail.com" }]
64
description = "A small example package"
75
name = "python_package"
86
# This means: Load the version from the package itself.
97
# See the section below: [tools.setuptools.dynamic]
10-
dynamic = ["version", # version is loaded from the package
11-
#"dependencies", # add if using requirements.txt
8+
dynamic = [
9+
"version", # version is loaded from the package
10+
#"dependencies", # add if using requirements.txt
1211
]
1312
readme = "README.md"
1413
requires-python = ">=3.9" # test all higher Python versions
@@ -44,20 +43,19 @@ docs = [
4443
"sphinx-copybutton",
4544
]
4645
# local development options
47-
dev = ["black[jupyter]", "ruff", "pytest"]
46+
dev = ["black[jupyter]", "ruff", "pytest", "isort", "jupytext"]
4847

49-
# Configure the Ruff linter: Ignore error number 501
5048
[tool.ruff]
5149
# https://docs.astral.sh/ruff/rules/#flake8-bandit-s
52-
# lint.ignore = ["E501"] # Ignore line length errors
53-
# Allow lines to be as long as (default is 88 in black)
5450

5551
[tool.ruff.lint]
5652
# https://docs.astral.sh/ruff/tutorial/#rule-selection
5753
# 1. Enable flake8-bugbear (`B`) rules
5854
# 2. Enable pycodestyle (`E`) errors and (`W`) warnings
5955
# 3. Pyflakes (`F`) errors
6056
extend-select = ["E", "W", "F", "B"]
57+
# Ignore line length errors:
58+
# ignore = ["E501"]
6159

6260
[build-system]
6361
build-backend = "setuptools.build_meta"

src/python_package/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
__version__ = metadata.version("python_package")
66

7-
from .mockup import hello_world
7+
from .mockup import hello_world, saved_world
88

99
# The __all__ variable is a list of variables which are imported
1010
# when a user does "from example import *"
11-
__all__ = ["hello_world"]
11+
__all__ = ["hello_world", "saved_world"]

src/python_package/mockup.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,33 @@ def hello_world(n: int) -> str:
1313
-------
1414
str
1515
str of 'hello world' n-times
16+
17+
Examples
18+
--------
19+
>>> hello_world(3)
20+
'hello world hello world hello world'
1621
"""
1722
return " ".join(repeat("hello world", n))
23+
24+
25+
def saved_world(filename: str) -> int:
26+
"""
27+
Count how many times 'hello world' is in a file.
28+
29+
Parameters
30+
----------
31+
filename : str
32+
The file to read
33+
34+
Returns
35+
-------
36+
int
37+
How many times 'hello world' is in the file
38+
39+
Examples
40+
--------
41+
>>> saved_world("not-real.txt") # doctest: +SKIP
42+
"""
43+
with open(filename, "r") as f:
44+
content = f.read()
45+
return content.count("hello world")

tests/README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
marp: true
3+
theme: uncover
4+
paginate: true
5+
backgroundColor: #fff
6+
---
7+
8+
# **Getting started with pytesting**
9+
10+
---
11+
12+
## Quick start
13+
14+
### Installation
15+
16+
Install pytest. e.g., from the "dev" dependencies
17+
18+
```bash
19+
pip install ".[dev]"
20+
```
21+
22+
### How to use
23+
24+
To execute the tests run e.g.
25+
26+
```bash
27+
pytest
28+
```
29+
30+
---
31+
32+
## Test development tips
33+
34+
---
35+
36+
### Folder and test naming
37+
38+
1. The tests for functions in `<filename>.py` should go in `tests/test_<filename>.py`
39+
40+
e.g., the tests for [python_package/mockup.py](../src/python_package/mockup.py) are in [tests/test_mockup.py](test_mockup.py)
41+
42+
2. The test names should start with `def test_<corresponding_function_name> ...`
43+
44+
e.g., `def test_hello_world(): ...`
45+
46+
---
47+
48+
### Some Pytest decorators
49+
50+
1. To indicate that the test function is expected to fail you can prepend
51+
52+
```python
53+
@pytest.mark.xfail(raises=TypeError)
54+
def test_hello_world_str(): ...
55+
```
56+
57+
---
58+
59+
2. To setup and cleanup any resources for a test you can use [pytest fixtures with `yield`](https://dev.to/dawidbeno/understanding-yield-in-pytest-fixtures-4m38)
60+
61+
```python
62+
@pytest.fixture
63+
def temp_file():
64+
# set up
65+
< code to create a file>
66+
# return
67+
yield
68+
the_file
69+
# clean up
70+
< code to remove the file>
71+
```
72+
73+
---
74+
75+
### Doctests
76+
77+
You can also include tests in your docstrings using `>>>` followed by the expected result e.g.
78+
79+
```python
80+
def hello_world(n):
81+
"""
82+
Prints 'hello world' n-times.
83+
...
84+
85+
Examples
86+
--------
87+
>>> hello_world(3)
88+
'hello world hello world hello world'
89+
...
90+
"""
91+
```
92+
93+
Needs `addopts = --doctest-modules` in `pytest.ini` in root of directory
94+
95+
---
96+
97+
### Skipping in doctests
98+
99+
If you know that the test cannot succeed but would like to include an example usage in the docstring still then you can add `# doctest: +SKIP` e.g.
100+
101+
```python
102+
def saved_world(filename):
103+
"""
104+
Count how many times 'hello world' is in a file.
105+
...
106+
107+
Examples
108+
--------
109+
>>> saved_world("not-real.txt") # doctest: +SKIP
110+
...
111+
"""
112+
```

tests/test_mockup.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
1-
from python_package import hello_world
1+
from python_package import hello_world, saved_world
2+
import pytest
23

34

45
def test_hello_world_3times():
56
expected = "hello world hello world hello world"
67
result = hello_world(3)
78
assert result == expected
9+
10+
11+
@pytest.mark.xfail(raises=TypeError)
12+
def test_hello_world_str():
13+
hello_world("3")
14+
15+
16+
@pytest.fixture
17+
def temp_file():
18+
# set up
19+
filename = "temp_hellooo.txt"
20+
with open(filename, "w") as f:
21+
f.write("hello world hello world hello world")
22+
yield filename
23+
# clean up
24+
import os
25+
26+
os.remove(filename)
27+
28+
29+
def test_saved_world_3times(temp_file):
30+
result = saved_world(temp_file)
31+
assert result == 3

0 commit comments

Comments
 (0)