Integrations: Python
Cranko supports Python projects set up using PyPA-compliant tooling. Because the Python packaging ecosystem contains a lot of variation, Cranko often needs you to give it a few hints to be able to operate correctly.
Autodetection
Cranko identifies Python projects by looking for directories containing files
named setup.py
, setup.cfg
, or pyproject.toml
. It is OK if one directory
contains more than one of these files.
Project Metadata
While the Python packaging ecosystem is moving towards standardized metadata
files, there are still lots of projects where the package name and version are
specified only in the setup.py
file. The only fully correct way to extract
these metadata would be to execute arbitrary Python code, which isn’t possible
for Cranko. Instead, Cranko uses a variety of more superficial techniques to try
extract project metadata.
Project name
- If there is a
pyproject.toml
file containing a keyname
in atool.cranko
section, that value is used as the project name. - Otherwise, if there is a
setup.cfg
file containing aname
key in ametadata
section, that value is used as the project name. - Otherwise, there should be a
setup.py
file containing a line with the following form:
Specifically, Cranko will search for a line containing a comment with the textproject_name = "myproject" # cranko project-name
cranko project-name
. Within such a line, it will then search for a string literal and extract its text as the project name. Cranko’s parsing of Python string literals is quite naive — escaped characters and the like won’t work.
Project version
Cranko will extract the project version from either setup.py
, or from an
arbitrary other Python file (i.e., from myproject/version.py
or something
similar). To tell Cranko to search for the version from a file other than
setup.py
, ensure that your project has a pyproject.toml
file and add an
entry of this form:
[tool.cranko]
main_version_file = "myproject/version.py"
The path should be relative to the directory containing the pyproject.toml
file.
Within that file, there are two options:
- If your project’s version is expressed as sys.version_info tuple, annotate
it with a comment containing the text
cranko project-version tuple
:
Cranko will parse the tuple contents into a PEP-440 version and rewrite it as needed. Note that some PEP-440 versions are not expressible as sys.version_info tuples. Also, Cranko’s tuple parser is quite naive, and only handles the most basic form of Python’s tuple, integer, and string literals. When your repo is bootstrapped, this line will be rewritten to look like:version_info = (1, 2, 0, 'final', 0) # cranko project-version tuple
because Cranko will start managing the version number.version_info = (0, 0, 0, 'dev', 0) # cranko project-version tuple
- If your project’s version is expressed as a string literal, annotate
it with a comment containing just the text
cranko project-version
:
Cranko will search for a string literal in the line and parse it as a PEP-440 version. Here too, Cranko’s parsing of the literal is quite naive and only handles the most basic forms. When your repo is bootstrapped, this line will be rewritten to look like:version = '1.2.0' # cranko project-version
because Cranko will start managing the version number.version = '0.dev0' # cranko project-version tuple
Additional Annotated Files
If there are files within your Python project besides setup.py
or your
main_version_file
that can provide useful metadata to Cranko — or will need
rewriting by Cranko to update versioning and/or dependency information — you
must tell Cranko which files it should check. Otherwise, Cranko would have to
scan every file in your repository, which would significantly slow it down with
large projects.
Tell Cranko which additional files to search by adding an annotated_files
key
to a tool.cranko
section in a pyproject.toml
file for your project:
[tool.cranko]
annotated_files = [
"myproject/npmdep.py",
"myproject/rustdep.py",
]
Internal Dependencies
“Internal” dependencies refer to monorepo situations where one repository contains more than one project, and some of those projects depend on one another.
Cranko actually doesn’t yet automatically recognize internal dependencies between multiple Python projects within one repository — the monorepo model seems to be extremely rare for Python packages. It does, however, recognize internal dependencies in a generic fashion that is useful if, for instance, your repo contains a JupyterLab extension that consists of a Python package that is tightly coupled to an NPM package.
Internal dependencies can be marked by tagging the dependency version requirement in one of your annotated files. Ensure that one or more of these files contains a line of code with the following form:
npm_requirement = '1.2.0' # cranko internal-req myfrontend
In this example, the python package has a dependency on the project name
myfrontend
, and that it requires version 1.2.0 or greater. (Here we envision
that the myfrontend
project is an NPM package, so that this version
requirement is a semver requirement.) As with other annotations, all that
Cranko does here is to search for something that looks like a string literal
within the tagged line, and attempt to parse it. As far as Cranko is concerned,
the only thing that matters in the annotated line is what happens inside the
string literal delimeters. You don’t need to do anything with the associated
variable (npm_requirement
), or even assign the string literal to a variable,
if it’s not needed in your code.
When you bootstrap your project, your tagged line will be rewritten to resemble something like:
npm_requirement = '0.0.0-dev.0' # cranko internal-req myfrontend
because Cranko takes over the expression of concrete version requirements in the repo.
Versioning internal dependencies
As described in Just-in-Time Versioning, Cranko needs the
version requirements of internal dependencies to be expressed as Git commits
rather than version numbers. These requirements must be expressed in the
pyproject.toml
file using the following structuring:
[tool.cranko.internal_dep_versions]
"myfrontend" = "2937e376b962162067135f3ac8b7b6a0f1c3efea"
This entry expresses that the Python project requires a release of the
myfrontend
package that contains the Git commit 2937e376...
. When Cranko
rewrites your project files during release processing, it will translate this
requirement into a concrete version number and update your annotated files
with the appropriate expression.
TODO: write some generic docs about these requirement expressions, and link to them from here.