Skip to main content

A simple Python library for creating and managing your configuration files

Project description

Homepy

Homepy is a simple Python library for creating and managing your configuration files.

Similar to something like Pulumi or SST, Homepy allows you to create an imperative-style generated configuration file.

Here's a simple example:

from pyhomedot import Home
from pyhomedot.resources import SymlinkResource, PackageResource

def main():
    home = Home()

    # we add any resources that we want to create/manage to the home object
    resources = [
        SymlinkResource("files/home/hello.txt", "hello.txt", force=True), # force=True will overwrite the file if it already exists
        SymlinkResource("files/git/", ".config/git/", force=True),
    ]

    if os.uname().sysname == "Darwin":
        resources.append(PackageResource("htop", "brew"))
    elif os.uname().sysname == "Linux":
        resources.append(PackageResource("htop", "apt"))

    for resource in resources:
        home.resources.append(resource)

    home.generate()

if __name__ == "__main__":
    main()

Prerequisites

Prerequisite macOS Linux (Debian/Ubuntu)
Python >= 3.13 brew install python@3.13 apt-get install python3.13 or via pyenv
uv brew install uv curl -LsSf https://astral.sh/uv/install.sh | sh
Homebrew (for brew provider) /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
APT (for apt provider) Pre-installed
mise (for mise provider) brew install mise curl https://mise.jdx.dev/install.sh | sh

Only the package manager prerequisites for providers you actually use are required. Python and uv are always required.

Then, all we need to do is run the script:

uvx --with pyhomedot python main.py

Since this is currently a thin wrapper around os commands and does not manage state, it will NOT remove anything automatically, such as if you remove a PackageResource. Instead, you will need to add installed=False to the PackageResource.

Resources

SymlinkResource

Creates symlinks from source files/directories to target locations relative to $HOME.

SymlinkResource("files/home/hello.txt", "hello.txt", force=True)
SymlinkResource("files/git/", ".config/git/")

If the source file or directory does not exist, a FileNotFoundError is raised.

PackageResource

Installs or uninstalls packages using one of the supported providers:

Provider Description
apt APT (Debian/Ubuntu)
brew Homebrew (macOS)
mise mise — installs globally via mise use -g

Versioning

You can optionally pin a package to a specific version:

PackageResource("node", "mise", version="20")       # mise use -g node@20
PackageResource("python", "brew", version="3.12")   # brew install python@3.12
PackageResource("htop", "apt", version="3.2.1")     # apt-get install htop=3.2.1

Each provider uses its native version syntax (@ for brew/mise, = for apt).

Casks (Homebrew)

To install a Homebrew cask instead of a formula, pass cask=True:

PackageResource("claude-code", "brew", cask=True)       # brew install --cask claude-code
PackageResource("firefox", "brew", cask=True, version="130")  # brew install --cask firefox@130

The cask option is only valid with the brew provider — using it with any other provider raises a ValueError.

TemplateResource

Like SymlinkResource, but renders variables into config files before writing them. Useful for dotfiles that need per-machine variation (e.g., injecting a username or hostname into a .gitconfig).

Uses {{ variable }} syntax for substitution:

TemplateResource(
    "gitconfig.tmpl",
    ".gitconfig",
    variables={"username": "John", "email": "john@example.com"},
    force=True,
)

Example template:

[user]
    name = {{ username }}
    email = {{ email }}

If the target file already exists, a warning is printed and the file is left unchanged. Use force=True to overwrite. If the source template does not exist, a FileNotFoundError is raised.

ShellResource

Runs arbitrary shell commands during generation. Covers configuration that goes beyond files and packages, such as defaults write on macOS or gsettings on Linux.

ShellResource("defaults write com.apple.dock autohide -bool true")
ShellResource("make build", cwd="/path/to/project")
ShellResource("echo $MY_VAR", env={"MY_VAR": "hello"})

You can optionally specify a working directory (cwd) and/or environment variables (env). If the command exits with a non-zero status, a RuntimeError is raised.

The Home Object

The Home class is the central orchestrator. You can add resources via home.resources.append() or via the convenience home.add() method, which accepts multiple resources and supports chaining:

home = Home()
home.add(
    SymlinkResource("bashrc", ".bashrc"),
    PackageResource("git", "apt"),
).add(
    ShellResource("echo done"),
)
home.generate()

Collision Handling

Homepy does not manage state, so collision handling is done at the resource level during generation.

SymlinkResource handles three cases when the target path already exists:

  1. Target is already a symlink to the correct source — skipped, no action taken.
  2. Target is a symlink to a different source — a warning is printed and the symlink is left unchanged. If force=True, the existing symlink is replaced.
  3. Target is a regular file or directory — a warning is printed and the file is left unchanged. If force=True, the existing file/directory is removed and replaced with the symlink.

If the target does not exist, the symlink is created directly.

TemplateResource warns and skips if the target file already exists. Use force=True to overwrite.

PackageResource delegates directly to the underlying package manager with no collision detection — if a package is already installed, the package manager handles it.

Dry-run Mode

A --dry-run flag that prints what would happen without making any changes. Especially useful since pyhomedot does not manage state and there is no built-in undo.

You can enable dry-run mode in two ways:

  1. CLI flag — pass --dry-run when running your script:

    uvx --with pyhomedot python main.py --dry-run
    
  2. Programmatically — pass dry_run=True to home.generate():

    home.generate(dry_run=True)
    

When home.generate() is called without an explicit dry_run argument, it automatically checks sys.argv for the --dry-run flag.

Vision

The goal of this project is to be a very lightweight way to manage everything. We strive to make sure that we use as few dependencies as possible.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyhomedot-1.6.0.tar.gz (19.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyhomedot-1.6.0-py3-none-any.whl (12.1 kB view details)

Uploaded Python 3

File details

Details for the file pyhomedot-1.6.0.tar.gz.

File metadata

  • Download URL: pyhomedot-1.6.0.tar.gz
  • Upload date:
  • Size: 19.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyhomedot-1.6.0.tar.gz
Algorithm Hash digest
SHA256 a478b49f0a8ce0d237d29cddd6cb16f106ef2e6a347c4a40572f830a730738c3
MD5 46ad375e1a7d99f47cd9dd8890421a53
BLAKE2b-256 b1e2163acbe187ec56f34f835831ac1fa2578310c02889c422c6bb0f66ad9763

See more details on using hashes here.

File details

Details for the file pyhomedot-1.6.0-py3-none-any.whl.

File metadata

  • Download URL: pyhomedot-1.6.0-py3-none-any.whl
  • Upload date:
  • Size: 12.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyhomedot-1.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5c0d8f6cb4c92fa5ef35caa6d809facedc6d3cd0a9942640da898a5706b5dbf3
MD5 a1d849e364cd31e0bd4e662bd0da233f
BLAKE2b-256 72c98f461220232c44b65c8b5cd1972b72d5c22a0b8e0a576a91804ec36b78f0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page