What is PyScaffold?
masterpip. It encourages the use of modern Python ecosystem tools and best practices to maintain productivity and project stability.repository·master·Indexed 25 days ago
https://github.com/pyscaffold/pyscaffoldA project generator for bootstrapping production-ready Python packages. PyScaffold automates the setup of testing, documentation, versioning, and CI/CD. It features an action-based pipeline for project generation, supports various dependency management workflows including pip-tools, Pipenv, and Conda, and provides the `putup` command to scaffold new projects.
pip. It encourages the use of modern Python ecosystem tools and best practices to maintain productivity and project stability.Extending PyScaffold relies on two fundamental concepts:
Extensions interact with these by implementing methods that intercept the pipeline to modify the structure or the actions themselves (e.g., using reject to prevent default files from being generated or modify to change the contents of an existing file).
PyScaffold represents a Python package project internally as a tree data structure. This tree is implemented as a nested dict where:
Note on versioning: Since version 4.0, the structure considers everything under the project folder, excluding the top-level project directory itself.
Example of a directory structure:
folder/ contains file.txt (content: "Hello World!") and another-folder/.folder/another-folder/ contains empty-file.txt (empty content).{
"folder": {
"file.txt": "Hello World!",
"another-folder": {
"empty-file.txt": ""
}
}
}PyScaffold uses setuptools_scm to infer the project version from Git tags.
MAJOR.MINOR[.PATCH] (e.g., 0.1.0 or 0.1).python -m setuptools_scm0.1.dev1+abc), you must create a Git tag before uploading a version to PyPI.python -m setuptools_scmPyScaffold projects typically involve two distribution stages:
sdist (Source Distribution): A platform-independent distribution generated from the original source code. It is considered best practice to upload an sdist to PyPI alongside your wheels to support platforms that cannot use pre-built wheels. PyScaffold uses setuptools-scm, so documentation and tests are included in the sdist by default.
wheel (Built Distribution): A platform-specific distribution that can contain highly optimized, pre-compiled files. Wheels are faster to install but may be tied to specific OSs or Python versions.
Key differences:
sdist is platform-independent; wheel is platform-specific.wheel files often have documentation and test files automatically removed, whereas sdist should include them whenever possible.To specify how a file should be written to disk, you can use a tuple as the value in the structure dictionary instead of a simple string.
In the tuple (content, operation):
callable (function) responsible for writing that content to the disk.If you provide only a string as the value, PyScaffold defaults to using pyscaffold.operations.create.
from pyscaffold.operations import create
{
"src": {
"namespace": {
"module.py": ('print("Hello World!")', create)
}
}
}Extensions are the preferred way to add new functionality or modify the action pipeline at runtime. They can be built-in (shipped with pyscaffold) or external (installed as separate Python packages).
Key Extension Mechanics:
setuptools entry points.putup parser.pyscaffold.actions to manipulate the action pipeline and the resulting project structure.PyScaffold does not attempt to replace dedicated tools like dependency managers or build systems. Instead, it provides sane default configurations for the most common Python ecosystem tools so they work together seamlessly.
By default, a generated project includes configurations for:
tox -e build, tox -e docs, or tox -e publish).black and adhering to PEP 8.PyScaffold is designed to be composable (it produces a standard Python package that interoperates with other tools), extensible (via a powerful extension system), and has no lock-in (PyScaffold is not a required install or development dependency of your project once generated).
PyScaffold generates projects using a sequence of steps called actions. Each action is a function that processes the project state and passes it to the next step in the pipeline.
An action must accept two arguments and return a tuple containing the same two types:
project_structure: A dictionary representing the project state (initially empty).options: A dictionary containing configuration options (parsed from CLI or defaults).Return Value: A tuple of (new_project_structure, new_options). Actions can also perform side effects, such as creating directories or initializing version control.
Actions are uniquely identified using the string format <module name>:<function name>. For example, an action named action inside extras.py of the pyscaffoldext.contrib package is identified as pyscaffoldext.contrib.extras:action.
def action(project_structure, options):
new_struct, new_opts = modify(project_structure, options)
some_side_effect()
return new_struct, new_optspretend flagWhen writing extensions, you must respect the pretend option. This flag indicates that actions should not actually run but should instead only report the expected results to the user. While PyScaffold handles this automatically for files in the project structure, complex custom actions require manual implementation. Use the pyscaffold.log.ReportLogger for logging in these scenarios.
update and force flagsConsider how your extension interacts with the update and force flags provided by the CLI.
${qual_pkg} in TemplatesWhen writing Mako templates, especially for packages within namespaces, avoid using the generic ${package} variable for imports. Instead, use the ${qual_pkg} variable, which contains the fully qualified package name including namespaces. This ensures imports remain correct in namespaced environments.
# Yes:
import ${qual_pkg}
from . import module
from ${qual_pkg}.module import function
# No:
import ${package}
from ${package}.module import functionTo generate files that adapt to project parameters (like package name or author), you can use the following for file content:
string.Template objects: PyScaffold uses safe_substitute to populate these templates.dict argument (containing pyscaffold.operations.ScaffoldOpts) and returns a str.These templates and functions are executed during the scaffolding process using the current project's options.
PyScaffold ≥ 3 uses a src directory to hold the actual Python package. This structure prevents common import errors where Python might import the local package directory instead of the installed version during testing.
Key Rules:
src are assumed to be part of the distributed package.src that are not meant for distribution (e.g., temporary files, local configs).docs folder or create a dedicated folder in the repository root (e.g., examples).