jwodder-pyrepo
is my personal command-line program for managing my Python
package repositories, including generating packaging boilerplate and performing
releases. It is heavily dependent upon the conventions I use in building &
structuring Python projects (documented in the repository wiki), and so it is not suitable for
general use.
jwodder-pyrepo
requires Python 3.10 or higher. Just use pip for Python 3 (You have pip, right?) to install it:
python3 -m pip install git+https://github.com/jwodder/pyrepo.git
pyrepo [<global-options>] <command> ...
All pyrepo
commands other than pyrepo init
must be run inside a Python
project directory (after processing the --chdir
option, if given); the
project root is determined by recursing upwards in search of a
pyproject.toml
file. Moreover, all commands other than pyrepo init
require that the project have already been set up by previously invoking
pyrepo init
.
-c FILE, --config FILE | |
Read configuration from FILE ; by default,
configuration is read from ~/.config/pyrepo.toml | |
-C DIR, --chdir DIR | |
Change to directory DIR before taking any further
actions | |
-l LEVEL, --log-level LEVEL | |
Set the logging level to the given value; default:
This option can be set via the configuration file. |
The configuration file (located at ~/.config/pyrepo.toml
by default) is a
TOML file with the following tables:
[options]
- Sets default values for global options
[options.COMMAND]
- (where
COMMAND
is the name of apyrepo
subcommand) Sets default values for options passed topyrepo COMMAND
.
Not all options can be configured via the configuration file; see the documentation for the respective options to find out which can.
Hyphens & underscores are interchangeable in option names in the configuration file.
The init
(if --github-user
is not specified), mkgithub
, and
release
subcommands make authenticated requests to the GitHub API and thus
require a GitHub access token. pyrepo
will automatically search for a
locally-stored token when needed by consulting the following sources:
- a
.env
file settingGH_TOKEN
orGITHUB_TOKEN
- the
GH_TOKEN
orGITHUB_TOKEN
environment variables - the gh command, if installed
- the hub command's configuration file
- the
hub.oauthtoken
Git config option
If no token is found in the above sources, pyrepo
will error out.
pyrepo [<global-options>] init [<options>] [<directory>]
Create packaging boilerplate for a new project (i.e., one that does not already
have a setup.py
, setup.cfg
, or pyproject.toml
file) in
<directory>
(default: the current directory). The project must be in a Git
repository and already contain Python source code (either one flat module or
else a package containing an __init__.py
file; either layout may optionally
be contained in a src/
directory). It is recommended to run this command
in a clean Git repository (i.e., one without any pending changes) so that the
command's effects can easily be reverted should anything go wrong.
pyrepo init
ensures the code uses a src/
layout — unless it's a flat
module, in which case the src/
layout is not used — and creates the
following files if they do not already exist:
.gitignore
.pre-commit-config.yaml
README.rst
pyproject.toml
tox.ini
If a LICENSE
file does not exist, one is created; otherwise, the copyright
years in the LICENSE
file are updated. In both cases, the copyright years
in the LICENSE
will contain the current year and all other years that
commits were made to the Git repository.
A boilerplate docstring and project data variables (__author__
,
__author_email__
, __license__
, __url__
, and __version__
) are
also added to the main source file (i.e., the only file if the project
is a flat module, or the {{import_name}}/__init__.py
file otherwise).
If there is a requirements.txt
file and/or a __requires__ =
list_of_requirements
assignment in the main source file, it is used to set
the project's dependencies in pyproject.toml
and then deleted. If both
sources of requirements are present, the two lists are combined, erroring if
the same package is given two different requirement specifications.
Finally, pre-commit install
is run, and a message is printed instructing
the user to run pre-commit run -a
after adding new files to the index.
All of the following can be set via the configuration file, in the
[options.init]
table.
--author NAME | Set the name of the project's author |
--author-email EMAIL | |
Set the project's author's e-mail address. This may be
either a plain e-mail address or a Jinja2 template
defined in terms of the variables project_name and
import_name . | |
--ci, --no-ci | Whether to generate templates for testing with GitHub
Actions; implies --tests ; default: --no-ci |
-c, --command NAME | |
If the project defines a command-line entry point, use
this option to specify the name for the command. The
entry point will then be assumed to be at either
IMPORT_NAME:main (if the code is a flat module) or
IMPORT_NAME.__main__:main (if the code is a
package). | |
-d TEXT, --description TEXT | |
Set the project's short description. If no description is specified on the command line, the user will be prompted for one. | |
--docs, --no-docs | |
Whether to generate templates for Sphinx documentation;
default: --no-docs | |
--doctests, --no-doctests | |
Whether to include running of doctests in the generated
testing templates; only has an effect when --tests
is also given; default: --no-doctests | |
--github-user USER | |
Set the username to use in the project's GitHub and Codecov URLs; when not set, the user's GitHub login is retrieved using the GitHub API | |
-p NAME, --project-name NAME | |
Set the name of the project as it will be known on PyPI; defaults to the import name. This can be set to a Jinja2 template defined in terms
of the variable | |
-P SPEC, --python-requires SPEC | |
Set the project's Besides setting | |
--repo-name NAME | |
The name of the project's repository on GitHub; defaults to the project name. This can be set to a Jinja2 template defined in terms
of the variables | |
--rtfd-name NAME | |
The name of the project's Read the Docs site; defaults to the project name. This can be set to a Jinja2 template defined in terms
of the variables | |
--tests, --no-tests | |
Whether to generate templates for testing with pytest
and tox; default: --no-tests | |
--typing, --no-typing | |
Whether to include configuration for type annotations
(creating a py.typed file, adding a typing
testenv to tox.ini if --tests is set, adding a
typing job to the CI configuration if --ci is
set, etc.); default: --no-typing |
pyrepo [<global-options>] add-ci-testenv <testenv> <python-version>
Configure the GitHub Actions test workflow to include a run of the tox
environment <testenv>
against <python-version>
.
pyrepo [<global-options>] add-pyversion <version> ...
Configure the project to declare support for and test against the given Python
version(s) (which must be given in the form "X.Y
").
Note that this command will not modify the project's requires-python
setting. If a given version is out of bounds for requires-python
, an error
will result; update requires-python
and try again.
pyrepo [<global-options>] add-typing
Add configuration for type annotations and the checking thereof:
- Add a
py.typed
file to the Python package (after converting from a flat module, if necessary) - Add a "
Typing :: Typed
" classifier to the project classifiers - Add a
mypy
configuration section topyproject.toml
- Add a
typing
testenv totox.ini
if tests are enabled - Add a
typing
job (run against the lowest supported Python version) to the CI configuration if it exists
pyrepo [<global-options>] begin-dev [<options>]
Prepare for development on the next version of a project by setting
__version__
to the next minor version number plus ".dev1" and adding a new
section to the top of the CHANGELOG (creating a CHANGELOG if necessary) and to
the top of docs/changelog.rst
(creating it if a docs
directory already
exists). This is the same behavior as the last step of pyrepo release
.
If the project uses versioningit, the __version__
variable is left alone.
If the project is already in "dev mode", nothing is done.
-N, --no-next-version | |
Do not calculate the next version for the
project: set __version__ (if not using
versioningit) to the current version plus
".post1" and omit the version from the new
CHANGELOG section |
pyrepo [<global-options>] drop-pyversion
Configure the project to no longer declare support for or test against the current lowest supported minor Python version.
It is an error to run this command when the project declares support for only zero or one minor Python version.
pyrepo [<global-options>] inspect
Examine a project repository and output its template variables as a JSON object. This command is primarily intended for debugging purposes.
pyrepo [<global-options>] mkgithub [<options>]
Create a new GitHub repository for the project; set the repository's
description to the project's short description; set the repository's topics to
the project's keywords plus "python"; create "dependencies",
"d:github-actions", and "d:python" labels in the repository (if
.github/dependabot.yml
exists); set the CODECOV_TOKEN
secret for GitHub
Actions (including for Dependabot); set the local repository's origin
remote to point to the GitHub repository; and push all branches & tags to the
remote.
--codecov-token SECRET | |
Value to use for the This option can be set via the | |
--no-codecov-token | |
Do not set the CODECOV_TOKEN secret. | |
-P, --private | Make the new repository private. |
--repo-name NAME | |
The name of the new repository; defaults to the repository name used in the project's URL. |
pyrepo [<global-options>] release [<options>] [<version>]
Create & publish a new release for a project. This command performs the following operations in order:
If the version for the new release is not specified on the command line, it is calculated by removing any prerelease & dev components from the project's current version
If the project does not use versioningit, set
__version__
to the version of the new releaseIf a CHANGELOG exists, set the date for the newest version section
If
docs/changelog.rst
exists, set the date for the newest version sectionUpdate the copyright year ranges in
LICENSE
and (if present)docs/conf.py
to include all years in which commits were made to the repositoryIf there is no CHANGELOG file, assume this is the first release and:
- Update the repostatus badge in the README from "WIP" to "Active"
- If the project does not have a "Private" classifier, remove the "work-in-progress" topic from the repository on GitHub and add the topic "available-on-pypi"
If the
--tox
option is given, run tox, failing if it failsBuild the sdist & wheel
Run
twine check
on the sdist & wheelCommit all changes made to the repository; the most recent CHANGELOG section is included in the commit message template. The commit is then tagged & signed.
- The release can be cancelled at this point by leaving the commit message unchanged.
- If the project uses
versioningit
, this step is moved to before building the sdist & wheel.
Push the commit & tag to GitHub
Convert the tag to a release on GitHub, using the commit message for the name and body
If the project does not have a "Private" classifier, upload the build assets to PyPI
Upload the build assets to GitHub as release assets
Prepare for development on the next version by setting
__version__
to the next minor version number plus ".dev1" and adding a new section to the top of the CHANGELOG (creating a CHANGELOG if necessary) and to the top ofdocs/changelog.rst
(creating it if adocs
directory already exists)If the project uses versioningit, the
__version__
variable is left alone.
--tox, --no-tox | |
Whether to run This option can be set via the configuration file. | |
--major | Set the release's version to the next major version |
--minor | Set the release's version to the next minor version |
--micro | Set the release's version to the next micro/patch version |
--post | Set the release's version to the next post version |
--date | Set the release's version to the current date in
YYYY.MM.DD format |
pyrepo [<global-options>] template [<options>] <templated-file> ...
Replace the given files with their re-evaluated templates.
-o FILE, --outfile FILE | |
Write output to <file> instead of overwriting the
file given on the command line. This option may only
be used when exactly one argument is given on the
command line. |
pyrepo [<global-options>] unflatten
Convert a "flat module" project (one where all the code is in a foobar.py
file) to a "package" project (one where all the code is in a src/foobar/
directory containing an __init__.py
file). The old flat module becomes the
__init__.py
file of the new package directory, and the project's
pyproject.toml
and tox.ini
are updated for the change in configuration.
jwodder-pyrepo
relies on various assumptions about project layout and
formatting; see the project wiki on GitHub for details. Most notably, it
does not support the following types of projects:
- projects that do not use hatch
- projects with packages that do not use a
src/
layout - projects with flat modules that use a
src/
layout - projects that neither store their version in a
__version__
variable in the initfile nor use versioningit - projects that are not pure Python
- projects containing more than one root-level module/package
- namespace packages
- (
pyrepo init
) projects that support Python 2 - (
pyrepo release
) projects that only support Python 2