{"id":34071210,"url":"https://github.com/mhered/mhered-test-pkg","last_synced_at":"2026-06-09T07:31:32.141Z","repository":{"id":50712622,"uuid":"519644391","full_name":"mhered/mhered-test-pkg","owner":"mhered","description":"A simple demo package to practice creating python packages. ","archived":false,"fork":false,"pushed_at":"2022-08-07T22:59:00.000Z","size":374,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-16T08:41:09.479Z","etag":null,"topics":["badges","codecov","coverage","mccabe","poetry","pre-commit","pypi","python","scriv","tox"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mhered.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-31T00:03:31.000Z","updated_at":"2022-08-07T23:15:43.000Z","dependencies_parsed_at":"2022-08-12T22:02:42.799Z","dependency_job_id":null,"html_url":"https://github.com/mhered/mhered-test-pkg","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/mhered/mhered-test-pkg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhered%2Fmhered-test-pkg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhered%2Fmhered-test-pkg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhered%2Fmhered-test-pkg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhered%2Fmhered-test-pkg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mhered","download_url":"https://codeload.github.com/mhered/mhered-test-pkg/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhered%2Fmhered-test-pkg/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34096950,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["badges","codecov","coverage","mccabe","poetry","pre-commit","pypi","python","scriv","tox"],"created_at":"2025-12-14T08:00:12.487Z","updated_at":"2026-06-09T07:31:32.120Z","avatar_url":"https://github.com/mhered.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mhered-test-pkg\n\nA simple demo package to practice creating python packages. \n\n[![][pypi-image]][pypi-url] [![][license-image]][license-url] [![][versions-image]][versions-url] [![][stars-image]][stars-url] [![][build-image]][build-url] [![][coverage-image]][coverage-url]\n\nInspired in this article: https://mathspp.com/blog/how-to-create-a-python-package-in-2022\n\nThe code implements a simple Rock, Paper, Scissors text-based game, loosely inspired in the one Al Sweigart wrote for Chapter 2 of  [Automate the boring stuff with Python](https://automatetheboringstuff.com/).\n\nInstallation:\n\n```bash\n$ pip install mhered-test-pkg\n```\n\nUsage:\n\n```bash\n$ rps\n```\n\nAlternatively\n\n```bash\n$ python3 -m mhered_test_pkg\n```\n\n## Creating a python package: Howto \n\n### Pick a name\n\nCheck that the name is available in [PyPI](https://pypi.org/)\n\n### Initialize `poetry`\n\nInstall `poetry` (I started from [here](https://python-poetry.org/docs/#osx--linux--bashonwindows-install-instructions) then modified the instructions):\n\n ```bash\n$ curl -sSL https://install.python-poetry.org/ | python3 -\n ```\n\nCreate a local folder for the project and initialize `poetry` inside\n\n```bash\n$ cd ~\n$ mkdir mhered-test-pkg\n$ cd mhered-test-pkg\n\n$ poetry new .\nCreated package mhered_test_pkg in .\n\n$ tree\n.\n├── mhered_test_pkg\n│   └── __init__.py\n├── pyproject.toml\n├── README.rst\n└── tests\n    ├── __init__.py\n    └── test_mhered_test_pkg.py\n```\n\nNote: I renamed `README.rst` to `README.md` to work in **Markdown**.\n\n```bash\n$ mv README.rst README.md\n```\n\nRun `poetry install` to create a file `poetry.lock` with the dependencies:\n\n```bash\n$ poetry install\n```\n\n### Initialize git in the local folder\n\nCreate an empty github repo: **[mhered-test-pkg](https://github.com/mhered/mhered-test-pkg)** and follow the instructions to set it as the remote and push a first commit with the local file structure:\n\n```bash\n$ git init\n$ git add *\n$ git commit -m \"First commit\"\n$ git branch -M main\n$ git remote add origin https://github.com/mhered/mhered-test-pkg.git\n$ git push -u origin main\n```\n\n### Set up `pre-commit` hooks\n\n  Add `pre-commit` as a development dependency, then commit updates:\n\n```bash\n$ poetry add -D pre-commit\n\n$ git add poetry.lock pyproject.toml\n$ git commit -m \"Add pre-commit devt dependency.\"\n```\n\nCreate a file `.pre-commit-config.yaml` in the root:\n\n```yaml\n# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.0.1\n    hooks:\n      - id: check-toml\n      - id: check-yaml\n      - id: end-of-file-fixer\n      - id: mixed-line-ending\n  - repo: https://github.com/psf/black\n    rev: 22.3.0\n    hooks:\n      - id: black\n  - repo: https://github.com/PyCQA/isort\n    rev: 5.10.1\n    hooks:\n      - id: isort\n        args: [\"--profile\", \"black\"]\n```\n\nActivate the `poetry` virtual environment to be able to use `pre-commit` then install the hooks and run them once: \n\n```bash\n$ poetry shell\n\n$ pre-commit install\n$ pre-commit run --all-files\n```\n\nNote: `pre-commit` is not found unless run from inside the shell - or you can use `poetry run pre-commit`\n\nCommit the changes (including the updates to `README.md`):  \n\n```bash\n$ git add *\n$ git commit -m \"Run all pre-commits.\"\n$ git push\n```\n\nNote: If some test fails the commit is cancelled and the files are automatically modified, so you need to add again the modified files and repeat the git commit - which is fine. However there is a strange behavior if the file was open: it seems to revert to an older version?\n\n### Add a license\n\n[Add a license from the github repo](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository) then pull changes to local. \n\nThis will add [LICENSE.md](./LICENSE.md)\n\n### Upload the TestPyPI\n\nDeclare the test repository https://test.pypi.org in `poetry` and name it  `testpypi`:\n\n```bash\n$ poetry config repositories.testpypi https://test.pypi.org/legacy/\n```\n\n[Create an account on TestPyPI](https://test.pypi.org/account/register/), go to Account Settings to get an API token and then configure `poetry` to use it:\n\n```bash\n$ poetry config http-basic.testpypi __token__ pypi-YOUR-TESTPYPI-API-TOKEN\n```\n\nNote: Be careful not to expose your API token, e.g. I wrote it to a `secrets.md` file then used `.gitignore` so as not to commit and publish it publicly.\n\nBuild and upload the package:\n\n```bash\n$ poetry build\n$ poetry publish -r testpypi\n```\n\nWith this our package is live in TestPyPI: https://test.pypi.org/project/mhered-test-pkg/\n\n\u003cimg src=\"assets/mhered-test-pkg.png\" alt=\"mhered-test-pkg\" style=\"zoom: 50%;\" /\u003e\n\nNote: Build creates the `dist/` folder that should be added to `.gitignore`\n\n```bash\n$ echo dist/ \u003e\u003e .gitignore\n\n$ git add .\n$ git commit -m \"Publish to TestPyPI\"\n$ git push\n```\n\n### Populate the package with code\n\nFor this example I wrote a simple Rock, Paper, Scissors game inspired and slightly refactored from the example proposed by Al Sweigart in his great book [Automate the boring stuff with Python](https://automatetheboringstuff.com/). The code goes in `mhered-test-pkg/__init__.py`.\n\n### Changelog management\n\nAdd [scriv](https://pypi.org/project/scriv/) for changelog management, as a development dependency with the `[toml]` extra :\n\n```\n$ poetry add -D scriv[toml]\n```\n\nConfigure `scriv` to use **Markdown** and add version numbering in the title by adding the following lines to the `pyproject.toml` file, refer to [scriv's readthedocs](https://scriv.readthedocs.io/en/latest/configuration.html):\n\n```toml\n[tool.scriv]\nformat = \"md\"\nversion = \"literal: pyproject.toml: tool.poetry.version\"\n```\n\nThen create the default directory for changelog fragments `changelog.d/`, and add an empty `.gitkeep`  file so that git tracks the empty folder.\n\n```bash\n$ mkdir changelog.d\n$ touch changelog.d/.gitkeep\n\n$ git add pyproject.toml poetry.lock changelog.d/.gitkeep\n$ git commit -m \"Add scriv as devt dependency.\"\n```\n\nCreate a new `.md` fragment file in the `changelog.d` folder: \n\n```bash\n$ scriv create\n```\n\nEdit it to add a description of the changes:\n\n```markdown\n### Added\n\n- A first simple implementation of Rock Paper Scissors\n```\n\nUpdate `README.md` and commit everything:\n\n```bash\n$ git add README.md changelog.d/* __init__.py\n$ git commit -m \"Simple Rock Paper Scissors game\"\n```\n\n### Publish the package to PyPI\n\nCreate a [PyPI](https://pypi.org/) account and API token, and configure `poetry` to use it:\n\n```bash\n$ poetry config pypi-token.pipy pypi-YOUR-PYPI-API-TOKEN\n```\n\nBuild and publish:\n\n```bash\n$ poetry publish --build\n```\n\n### Do a victory lap\n\nInstall, import and uninstall the package (outside of the shell) to check it works. Note: the hyphens (`-`) in the package name turn into underscores (`_`) in the module name.\n\n```bash\n$ pip install mhered-test-pkg\nDefaulting to user installation because normal site-packages is not writeable\nCollecting mhered-test-pkg\n  Using cached mhered_test_pkg-0.1.0-py3-none-any.whl (2.5 kB)\nInstalling collected packages: mhered-test-pkg\nSuccessfully installed mhered-test-pkg-0.1.0\n\n$ python3 -m mhered_test_pkg\nROCK, PAPER, SCISSORS\n0 Wins, 0 Losses, 0 Ties\nEnter your move: (r)ock (p)aper (s)cissors or (q)uit\nr\nROCK versus... SCISSORS\nYou win!\n1 Wins, 0 Losses, 0 Ties\nEnter your move: (r)ock (p)aper (s)cissors or (q)uit\nq\nBye!\n\n$ pip uninstall mhered-test-pkg\n```\n\n###  Publish a release\n\nAdd a description, installation and usage instructions in the `README.md` and declare it in `pyproject.toml`:\n\n```toml\nreadme = \"README.md\"\n```\n\nMake `scriv` collect the previously created changelog fragment to a new `CHANGELOG.md` file with:\n\n```bash\n$ scriv collect\n```\n\nLets commit: \n\n```bash\n$ git add CHANGELOG.md README.md pyproject.toml\n$ git commit -m \"Prepare release 0.1.0\"\n```\n\nTag the commit, and push the tag to the remote (seen [here](https://stackabuse.com/git-push-tags-to-a-remote-repo/)):\n\n```bash\n$ git tag -a v0.1.0 -m \"Initial version\"\n$ git push origin v0.1.0\n```\n\nI discovered I hadn't properly configured version numbering for scriv ( had forgotten to add `version = \"literal: pyproject.toml: tool.poetry.version\"` under `[tool.scriv]` in `pyproject.toml` ), so I did it now, and added a new release to test it. I bumped the version in `pyproject.toml` to  `0.1.1` with:\n\n```bash\n$ poetry version patch\nBumping version from 0.1.0 to 0.1.1\n```\n\nNext add a changelog fragment:\n\n```bash\n$ scriv create\n```\n\nand edit it to describe the change\n\n```markdown\n### Fixed\n\n- Configure `scriv` to get version number from `pyproject.toml`\n```\n\nThen I manually increased the version in `mhered-test-pkg/__init__.py` `__version__ = \"0.1.1\"`and added a test to check it is always in sync with `tool.poetry.version` in `pyproject.toml` ([there seems to be no better way](https://github.com/python-poetry/poetry/issues/144#issuecomment-877835259))\n\n```python\nimport toml\nfrom pathlib import Path\nimport mhered_test_pkg\n\ndef test_versions_are_in_sync():\n    \"\"\" Checks if tool.poetry.version in pyproject.toml and\n    \t__version__ in mhered_test_pkg.__init__.py are in sync.\"\"\"\n\n    path = Path(__file__).resolve().parents[2] / \"pyproject.toml\"\n    pyproject = toml.loads(open(str(path)).read())\n    pyproject_version = pyproject[\"tool\"][\"poetry\"][\"version\"]\n\n    init_py_version = mhered_test_pkg.__version__\n    \n    assert init_py_version == pyproject_version\n```\n\nAdd a new changelog fragment and edit it to describe the change\n\n```bash\n$ scriv create --edit\n```\n\n```markdown\n## Added\n\n- Test to check that versions defined in `pyproject.py` and `__init__.py` are in sync\n```\n\nUpdate the Changelog:\n\n```bash\n$ scriv collect\n```\n\nCommit and push, tag and push:\n\n```bash\n$ git add pyproject.toml mhered_test_pkg/__init__.py tests/test_mhered_test_pkg.py CHANGELOG.md README.md\n\n$ git commit -m \"Configure versions in scriv\"\n$ git push\n\n$ git tag -a v0.1.1 -m \"Configure versions in scriv\"\n$ git push origin v0.1.1\n\n$ scriv github-release -v  DEBUG\ndebug: Running command 'git tag'\ndebug: Command exited with 0 status. Output: 'v0.1.0\\nv0.1.1\\n'\ndebug: Running command ['git', 'config', '--get-regex', 'remote[.].*[.]url']\ndebug: Command exited with 0 status. Output: 'remote.origin.url https://github.com/mhered/mhered-test-pkg.git\\n'\ndebug: Starting new HTTPS connection (1): api.github.com:443\ndebug: https://api.github.com:443 \"GET /repos/mhered/mhered-test-pkg/releases HTTP/1.1\" 200 600\nwarning: Version 0.1.1 has no tag. No release will be made.\nwarning: Version 0.1.0 has no tag. No release will be made.\n\n```\n\nIt still does not work... to be continued.\n\n### Troubleshooting\n\nThere is something off with the tag `v0.1.0`. Was linked to `8ad878f` which does not show here!\n\n```bash\n$ git log --oneline\n5979855 (HEAD -\u003e main, origin/main) Update README.md\nfa7a843 (tag: v0.1.1) Configure versions in scriv\n74b8fcb Prepare release 0.1.0\n9519229 Add victory lap to README.md\nc22f560 Move __init__.py to mhered_test_pkg/\n066172e Minor updates to README.md\ne438080 Simple Rock Paper Scissors game\n2c93077 Add scriv as devt dependency\n262bb9e Publish to TestPyPI\ndd9cfd9 Add .gitignore to protect API tokens\n81fa08d Minor change of README.md\n4d8b935 Update License in README.md\ne18df98 Create LICENSE.md\n9400227 Update README.md\n3d3a9d8 Run all pre-commits.\n8de2b5a Add pre-commit devt dependency\n9743aef First commit\n```\n\nlets repair the tag `v0.1.0`:\n\n```bash\n$ git tag -d v0.1.0\t\t\t\t\t\t# delete the old tag locally\nDeleted tag 'v0.1.0' (was 8ad878f)\n$ git push origin :refs/tags/v0.1.0\t\t# delete the old tag remotely\nTo https://github.com/mhered/mhered-test-pkg.git\n - [deleted]         v0.1.0\n$ git tag -a v0.1.0 74b8fcb\t\t\t\t# make a new tag locally\n$ git push origin v0.1.0\t\t\t\t# push the new local tag to the remote\n```\n\nVoilá:\n\n```bash\n$ git log --oneline\n5979855 (HEAD -\u003e main, origin/main) Update README.md\nfa7a843 (tag: v0.1.1) Configure versions in scriv\n74b8fcb (tag: v0.1.0) Prepare release 0.1.0\n9519229 Add victory lap to README.md\nc22f560 Move __init__.py to mhered_test_pkg/\n066172e Minor updates to README.md\ne438080 Simple Rock Paper Scissors game\n2c93077 Add scriv as devt dependency\n262bb9e Publish to TestPyPI\ndd9cfd9 Add .gitignore to protect API tokens\n81fa08d Minor change of README.md\n4d8b935 Update License in README.md\ne18df98 Create LICENSE.md\n9400227 Update README.md\n3d3a9d8 Run all pre-commits.\n8de2b5a Add pre-commit devt dependency\n9743aef First commit\n```\n\n### Write tests\n\nI added a couple of tests, refactored a bit the code and committed the changes.\n\nThen, to create a release there are a few steps: \n\n- bump the  version\n- edit `__init__.py` manually to sync version number\n- run the tests\n- create a fragment - with `--edit` option to launch the editor directly\n- collect all fragments to `CHANGELOG.md`\n- commit changes to `pyproject.toml README.md CHANGELOG.md mhered_test_pkg/__init__.py`\n- create tag\n- push commit and tag\n\n```bash\n$ git commit -a -m \"Add tests\"\n$ poetry version patch\n$ atom mhered_test_pkg/__init__.py\n$ pytest\n============================= test session starts =============================\nplatform linux -- Python 3.8.10, pytest-5.4.3, py-1.11.0, pluggy-0.13.1\nrootdir: /home/mhered/mhered-test-pkg\ncollected 4 items                         \ntests/test_mhered_test_pkg.py ....                                       [100%]\n============================== 4 passed in 0.02s ==============================\n$ scriv create --edit\n$ scriv collect\n\n$ git commit -m \"Prepare release 0.1.2\"\n$ git push\n\n$ git tag -a 0.1.2 -m \"Add tests\"\n$ git push origin 0.1.2\n```\n\n* try to create a release with `scriv github-release`\n\nThis time `$ scriv github-release -v  DEBUG` gives an error message so I rename tag `0.1.2` to `v0.1.2`:\n\n```bash\n$ git tag v0.1.2 0.1.2^{}\n$ git tag -d 0.1.2\n$ git push origin :refs/tags/0.1.2\n$ git log  --oneline\naa3e44a (HEAD -\u003e main, tag: v0.1.2, origin/main) Prepare release 0.1.2\nb03efa8 Add tests\nd688927 Passes basic tests\n5979855 Update README.md\nfa7a843 (tag: v0.1.1) Configure versions in scriv\n74b8fcb (tag: v0.1.0) Prepare release 0.1.0\n9519229 Add victory lap to README.md\n...\n```\n\nBack to normal:\n\n```bash\n$ scriv github-release -v DEBUG\ndebug: Running command 'git tag'\ndebug: Command exited with 0 status. Output: 'v0.1.0\\nv0.1.1\\nv0.1.2\\n'\ndebug: Running command ['git', 'config', '--get-regex', 'remote[.].*[.]url']\ndebug: Command exited with 0 status. Output: 'remote.origin.url https://github.com/mhered/mhered-test-pkg.git\\n'\ndebug: Starting new HTTPS connection (1): api.github.com:443\ndebug: https://api.github.com:443 \"GET /repos/mhered/mhered-test-pkg/releases HTTP/1.1\" 200 717\nwarning: Version 0.1.2 has no tag. No release will be made.\nwarning: Version 0.1.1 has no tag. No release will be made.\nwarning: Version 0.1.0 has no tag. No release will be made.\n```\n\nApparently this is because I need to add my PAT as environment variable `GITHUB_TOKEN`, see [scriv docs](https://scriv.readthedocs.io/en/latest/commands.html#scriv-github-release). I tried though, and it does not work... I create the release manually in github.\n\n### Automating with `tox`\n\n`tox` automates testing, linting, formatting, test coverage, documentation, etc.\n\nWe add tox as a development dependency:\n\n```bash\n$ poetry add -D tox\n```\n\nConfiguration is done in a `tox.ini` file in `toml` format, which can be initiated in its simplest form running  `$ tox-quickstart` and answering a few questions.\n\n```toml\n# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will run the\n# test suite on all supported python versions. To use it, \"pip install tox\"\n# and then run \"tox\" from this directory.\n\n[tox]\nenvlist = py37\n\n[testenv]\ndeps =\n    pytest\ncommands =\n    pytest\n```\n\nHowever this simplest version did not work. I had to do a few iteratons:\n\n* Added `isolated_build = True` to work with `poetry`\n* Added `toml` to dependencies otherwise `pytest`  cannot import it. It seems that `tox` creates its own virtual environments and you need to add all dependencies again...\n* Added formatting with `black`, linting with `flake8`, `pylint`, `mccabe`, sorting of imports with [isort](https://pycqa.github.io/isort/), and testing with `pytest`. [mccabe](https://pypi.org/project/mccabe/) is a `flake8` plugin to check the code's McCabe complexity (should be \u003c10). \n* `black --check` does not modify the files, only exits with an error if the check is not passed. What is the point?\n\nResult:\n\n```toml\n# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will run the\n# test suite on all supported python versions. To use it, \"pip install tox\"\n# and then run \"tox\" from this directory.\n\n[tox]\nisolated_build = True\nenvlist = py38\n\n[testenv]\ndeps =\n    toml\n    black\n    flake8\n    isort\n    mccabe\n    pylint\n    pytest\n\ncommands =\n    black --check mhered_test_pkg\n    isort  --check mhered_test_pkg\n    flake8 mhered_test_pkg --max-complexity 10\n    pylint mhered_test_pkg\n    pytest .\n```\n\n Execute with: \n\n```bash\n$ tox\n```\n\nQuestion: why use `tox` when I can use a `poetry` script or even better a `pre-commit` hook? \n\n### Check coverage\n\nInstallation and basic execution of [coverage](https://coverage.readthedocs.io/en/6.4.2/index.html):\n\n```bash\n$ poetry add -D coverage\n$ coverage run -m pytest\n$ coverage report\nName                            Stmts   Miss  Cover\n---------------------------------------------------\nmhered_test_pkg/__init__.py        49     38    22%\ntests/__init__.py                   0      0   100%\ntests/test_mhered_test_pkg.py      15      0   100%\n---------------------------------------------------\nTOTAL                              64     38    41%\n\n```\n\nWe can execute the checks in a more nuanced way including all files in the package and all branching paths, and we can also generate nicer HTML reports [such as this one](./assets/sample_html_coverage_report/index.html) as follows:\n\n```bash\n$ coverage run --source=mhered_test_pkg --branch -m pytest .\n$ coverage html\n```\n\n![22pc_coverage](assets/22pc_coverage.png)\n\nTo add `coverage` to `tox` modify `tox.ini` to add the relevant lines:\n\n```toml\n# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will run the\n# test suite on all supported python versions. To use it, \"pip install tox\"\n# and then run \"tox\" from this directory.\n\n[tox]\nisolated_build = True\nenvlist = py38\n\n[testenv]\ndeps =\n    toml\n    black\n    flake8\n    isort\n    mccabe\n    pylint\n    pytest\n    coverage # development dependency\n\ncommands =\n    black --check mhered_test_pkg\n    isort  --check mhered_test_pkg\n    flake8 mhered_test_pkg --max-complexity 10\n    pylint mhered_test_pkg\n    coverage run --source=mhered_test_pkg --branch -m pytest . # execute\n    coverage report -m --fail-under 90 # report \u0026 fail below 90%\n\n```\n\nAdded tests to increase coverage up to 97% - i.e. all lines of code covered except the case `\"__name__\" == \"__main__:\"` because tests import as module.\n\n![97pc_coverage](assets/97pc_coverage.png)\n\nIn the process I learned about monkeypatching user input, using iterators to simulate a sequence of inputs, or testing for sys exit, see references in [./tests/test_mhered_test_pkg.py](./tests/test_mhered_test_pkg.py)\n\nUsual ritual to create a release. As some of the steps modify files that need to be committed this process ends up being iterative. I try to keep it clean using profusely `$ git commit --amend`along the way:\n\n```bash\n$ scriv create --edit # describe changes in a fragment\n$ poetry version patch # bump version\n$ atom mhered_test_pkg/__init__.py # sync _version__\n$ scriv collect # update CHANGELOG.md\n$ git add . \n$ git commit\n$ git tag -a v0.1.3 -m \"97% test coverage\"\n$ git push\n$ git log --oneline\n91c4aa1 (HEAD -\u003e main, tag: v0.1.3, origin/main) 0.1.3 automated with tox and 97% coverage\nd6670c4 Automating with tox\n028028a Update README.md\naa3e44a (tag: v0.1.2) Prepare release 0.1.2\nb03efa8 Add tests\nd688927 Passes basic tests\n5979855 Update README.md\nfa7a843 (tag: v0.1.1) Configure versions in scriv\n74b8fcb (tag: v0.1.0) Prepare release 0.1.0\n...\n$ git push origin v0.1.3\n```\n\n### CI/CD with GitHub Actions\n\n[GitHub Actions](https://docs.github.com/en/actions) allow automating workflows of actions that are triggered by certain events e.g. a commit pushed to the repo, a pull request or a release. They are defined in YAML files that live in the directory `.github/workflows`. \n\nThe CI runner spawns the full environment in a Github server, including setting up the OS with`runs-on:`, installing and activating python with `uses: actions/setup-python@v2` ... `with:`  ... `python-version:`...`\"3.8\"`, installing dependencies (via `tox` or `poetry` commands) and downloading our repository with `uses: actions/checkout@v2`\n\nA nice intro tutorial in Youtube:  https://www.youtube.com/watch?v=R8_veQiYBjI\n\n```yaml\n# .github/workflows/CI.yaml\nname: mhered-test-pkg CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.8\", \"3.9\", \"3.10\"]\n\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v2\n\n      - name: Setup Python\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          python -m pip install tox tox-gh-actions\n\n      - name: Run tox\n        run: tox\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v2\n        with:\n            fail_ci_if_error: true\n\n```\n\nEverything seems to be working including Codecov integration except PR comments.\n\n## Automatic publishing of releases to PyPI\n\nI wrote a new `PyPI_publish.yaml` file with the following steps: \n\n1. Checkout the repo\n2. Set up Python 3.8\n3. Install Poetry and dependencies\n4. Configure Poetry with a PyPI token\n5. Build and publish the package\n\nA more straighforward solution using a pre-made GH action from PyPA to upload both to Test PyPI and PyPI is here: https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/\n\nNote: add the Test PyPI and PyPI credentials created earlier as repository secrets in Github as `PYPI_TOKEN` and `TEST_PYPI_TOKEN`: **Settings** --\u003e **Secrets** --\u003e **Actions** --\u003e **Add new repository secret**.\n\n```yaml\n# .github/workflows/PyPI_publish.yaml\nname: publish mhered-test-pkg to PyPI\n\non:\n  release:\n    types: [published]\n    branches: [ main ]\n  workflow_dispatch:\n\njobs:\n  build-and-publish:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v2\n\n      - name: Setup Python\n        uses: actions/setup-python@v2\n        with:\n          python-version: \"3.8\"\n\n      - name: Install poetry and dependencies\n        run: |\n          python -m pip install --upgrade pip\n          python -m pip install poetry\n\n      - name: Configure poetry\n        env:\n          pypi_token: ${{ secrets.PYPI_TOKEN }}\n        run: poetry config pypi-token.pypi $pypi_token\n\n      - name: Build and publish\n        run: poetry publish --build\n```\n\nNew release:\n\n```bash\n$ poetry version patch\n$ atom mhered_test_pkg/__init__.py\n$ pytest\n$ scriv create --edit\n$ scriv collect\n$ git add .\n$ git commit -m \"Add CI/CD and Codecov - release 0.1.4\"\n$ git push\n\n$ git tag -a v0.1.4 -m \"Add GH actions for CI/CD and Codecov integration\"\n$ git push origin v0.1.4\n```\n\nDid not work initially. I found three typos  in `PyPI_publish.yaml`, should be: \n\n- `secrets.PYPI_TOKEN` instead of `secrets.PyPI_TOKEN`\n- `run: poetry config pypi-token.pypi $pypi_token` instead of `pypi_token.pypi`\n-  `run: poetry publish --build` instead of `uses:` \n\nI also added minor updates to `README.md` and I created a few releases in the process.\n\n```bash\n$ poetry version patch\n$ atom mhered_test_pkg/__init__.py\n$ pytest\n$ scriv create --edit\n$ scriv collect\n$ git add .\n$ git commit -m \"Commit Message - release 0.1.X\"\n$ git push\n\n$ git tag -a v0.1.X -m \"Release Message\"\n$ git push origin v0.1.X\n```\n\nFinally succeeded with `v0.1.7`\n\n### Add badges to README.md\n\nPiece of cake following the instructions in https://shields.io/\n\n### Move code to `/src`\n\nAd adapt everywhere:\n\n* in tests change the import statement:\n\n```python\nfrom src.mhered_test_pkg import ...\n```\n\n* in `tox.ini` replace `mhered_test_pkg` by `src` \n\n```toml\n...\ncommands =\n    black --check src\n    isort  --check src\n    flake8 src --max-complexity 10\n    pylint src\n    coverage run --source=src --branch -m pytest .\n    coverage report -m --fail-under 60\n    coverage xml\n...\n```\n\n### Add entry points\n\nIn development, as an alternative to calling:\n\n```bash\n$ python3 ./src/mhered_test_pkg/__init__.py\n```\n\n1. We can create a `__main__.py` file:\n\n```python\n\"\"\" __main__ entry point \"\"\"\nfrom mhered_test_pkg import rock_paper_scissors\n\nif __name__ == \"__main__\":\n    rock_paper_scissors()\n```\n\nto allow calling the package as a module:\n\n```bash\n$ python3 -m src.mhered_test_pkg\n```\n\nAnd/or \n\n2. edit `pyproject.py` to add the following line:\n\n```toml\n[tool.poetry.scripts]\nrps = \"mhered_test_pkg.__init__:rock_paper_scissors\n```\n\nto create a shortcut to execute the game writing:\n\n```bash\n$ rps\n```\n\nNote that when the app is distributed the methods that work are different! - cfr. the ones described at the beginning of the `README.md`\n\nTime to make a new release 0.1.8...\n\n### Tidy up\n\nI noticed some issues due to discrepancies between the linting in pre-commit and the one in tox. For instance isort and black seem to be doing incompatible changes in one of the improts in tests.\n\nTo add all the linting to pre-commit, we edit `.pre-commit-config.yaml`:\n\n```yaml\n# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.0.1\n    hooks:\n      - id: check-toml\n      - id: check-yaml\n      - id: end-of-file-fixer\n      - id: mixed-line-ending\n\n  - repo: https://github.com/psf/black\n    rev: 22.3.0\n    hooks:\n      - id: black\n\n  - repo: https://github.com/PyCQA/isort\n    rev: 5.10.1\n    hooks:\n      - id: isort\n        args: [\"--profile\", \"black\"]\n\n  - repo: https://github.com/PyCQA/flake8\n    rev: 4.0.1\n    hooks:\n      - id: flake8\n        additional_dependencies: [mccabe]\n        args: [\"--max-line-length\", \"88\", \"--max-complexity\", \"10\"]\n\n  - repo: https://github.com/PyCQA/pylint/\n    rev: v2.14.5\n    hooks:\n      - id: pylint\n        exclude: tests/  # Prevent files in tests/ to be passed in to pylint.\n```\n\nModify `tox.ini` to create dedicated environments for linting and coverage and execute them only with python 3.8 to avoid running them multiple times. The linting one simple calls pre-commit:\n\n```toml\n# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will run the\n# test suite on all supported python versions. To use it, \"pip install tox\"\n# and then run \"tox\" from this directory.\n\n[tox]\nisolated_build = True\nenvlist =\n   py38,\n   py39,\n   py310,\n   linting,\n   coverage,\n\n[testenv]\ndeps =\n    toml\n    pytest\nchangedir = {envtmpdir}\ncommands =\n    pytest {toxinidir}\n\n[testenv:linting]\ndeps = pre-commit\ncommands = pre-commit run --all-files --show-diff-on-failure\n\n[testenv:coverage]\ndeps =\n    toml\n    pytest\n    coverage\ncommands =\n    coverage run --source=src --branch -m pytest {toxinidir}\n    coverage report -m --fail-under 90\n    coverage xml -o {toxinidir}/coverage.xml\n\n[gh-actions]\npython =\n    3.8: py38, linting, coverage\n    3.9: py39\n    3.10: py310\n```\n\nFinally modify the GH action `CI.yaml` to upload to codecov only when running Python 3.8. In this case we use a premade GH action instead of running manually the commands:\n\n```yaml\n      ...\n      - name: Upload coverage to Codecov\n        # Only generate the coverage report in Python 3.8\n        if: \"matrix.python-version == '3.8'\"\n        uses: codecov/codecov-action@v2\n        with:\n          fail_ci_if_error: true\n```\n\nAnd with this we are ready to publish the final release 0.1.10.\n\n\n\n\u003c!-- Badges: --\u003e\n\n[pypi-image]: https://img.shields.io/pypi/v/mhered-test-pkg\n[pypi-url]: https://pypi.org/project/mhered-test-pkg/\n[build-image]: https://github.com/mhered/mhered-test-pkg/actions/workflows/CI.yaml/badge.svg\n[build-url]: https://github.com/mhered/mhered-test-pkg/actions/workflows/CI.yaml\n[coverage-image]: https://codecov.io/gh/mhered/mhered-test-pkg/branch/main/graph/badge.svg\n[coverage-url]: https://codecov.io/gh/mhered/mhered-test-pkg/\n[stars-image]: https://img.shields.io/github/stars/mhered/mhered-test-pkg\n[stars-url]: https://github.com/mhered/mhered-test-pkg\n[versions-image]: https://img.shields.io/pypi/pyversions/mhered-test-pkg\n[versions-url]: https://pypi.org/project/mhered-test-pkg/\n[license-image]: https://img.shields.io/github/license/mhered/mhered-test-pkg\n[license-url]: https://github.com/mhered/mhered-test-pkg/blob/main/LICENSE.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhered%2Fmhered-test-pkg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmhered%2Fmhered-test-pkg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhered%2Fmhered-test-pkg/lists"}