PyScaffold Documentation

repository·master·Indexed 25 days ago

https://github.com/pyscaffold/pyscaffold

A 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.

Tokens
19.7K
Snippets
62
Records
119
Agent score
77%

What's inside PyScaffold

  1. What is PyScaffold?

    master
    PyScaffold is a project generator designed to bootstrap high-quality Python packages. It automates the creation of project structures that are ready to be shared on PyPI and installed via pip. It encourages the use of modern Python ecosystem tools and best practices to maintain productivity and project stability.
  2. Core concepts: Project structure and Action pipeline

    master

    Extending PyScaffold relies on two fundamental concepts:

    1. Project structure representation: An in-memory model of the files and directories that PyScaffold intends to create.
    2. Action pipeline: The mechanism through which PyScaffold executes file operations.

    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).

  3. Understand the project structure tree representation

    master

    PyScaffold represents a Python package project internally as a tree data structure. This tree is implemented as a nested dict where:

    • Keys represent the file system path (relative to the project folder).
    • Values represent the file content or a nested dictionary for sub-directories.

    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": ""
            }
        }
    }
  4. Manage project versioning with Git tags

    master

    PyScaffold uses setuptools_scm to infer the project version from Git tags.

    1. Tagging: Use the format MAJOR.MINOR[.PATCH] (e.g., 0.1.0 or 0.1).
    2. Retrieving version: You can check the current PEP 440-compliant version using:
      python -m setuptools_scm
    3. PyPI Requirement: Because PyPI rejects local versions (like 0.1.dev1+abc), you must create a Git tag before uploading a version to PyPI.
    python -m setuptools_scm
  5. Understanding sdist vs wheel in PyScaffold

    master

    PyScaffold projects typically involve two distribution stages:

    1. 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.

    2. 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:

    • Platform Support: sdist is platform-independent; wheel is platform-specific.
    • Content: wheel files often have documentation and test files automatically removed, whereas sdist should include them whenever possible.
  6. Use file operations in the project structure

    master

    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):

    1. The first element is the file content.
    2. The second element is a 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)
            }
        }
    }
  7. How Extensions work in PyScaffold

    master

    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:

    • Discovery: PyScaffold dynamically discovers installed extensions using setuptools entry points.
    • CLI Integration: Extensions are required to add at least one CLI argument to allow users to opt-in to their behavior. These arguments are automatically added to the main putup parser.
    • Pipeline Manipulation: Once activated, extensions can use helper functions from pyscaffold.actions to manipulate the action pipeline and the resulting project structure.
  8. Understand PyScaffold's tool integration and philosophy

    master

    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:

    • setuptools: For building Python packages.
    • Sphinx: For documentation.
    • pytest: For testing.
    • tox: For task running and testing across environments (e.g., tox -e build, tox -e docs, or tox -e publish).
    • pre-commit: For running linters and formatters like 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).

  9. How the PyScaffold Action Pipeline works

    master

    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.

    Action Signature

    An action must accept two arguments and return a tuple containing the same two types:

    1. project_structure: A dictionary representing the project state (initially empty).
    2. 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.

    Action Identifiers

    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_opts
  10. Best Practices for Writing Extensions

    master

    Respect the pretend flag

    When 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.

    Handle update and force flags

    Consider how your extension interacts with the update and force flags provided by the CLI.

    Use ${qual_pkg} in Templates

    When 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 function
  11. Customize file content using templates or functions

    master

    To generate files that adapt to project parameters (like package name or author), you can use the following for file content:

    1. string.Template objects: PyScaffold uses safe_substitute to populate these templates.
    2. Functions: A function that accepts a single 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.

  12. Understand the PyScaffold src layout

    master

    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:

    • All files inside src are assumed to be part of the distributed package.
    • Do not include files in src that are not meant for distribution (e.g., temporary files, local configs).
    • For extra files not meant for distribution, use the docs folder or create a dedicated folder in the repository root (e.g., examples).