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.7.0.tar.gz (20.0 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.7.0-py3-none-any.whl (12.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyhomedot-1.7.0.tar.gz
  • Upload date:
  • Size: 20.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","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.7.0.tar.gz
Algorithm Hash digest
SHA256 7567a840dbc2fd79b90b239c4680c8c92d6d4aabb4134d957b05fe21725eb240
MD5 f8d2e3b622ac2636107c466307c18b5f
BLAKE2b-256 14bee4d44dce64bbebd2e5915a042a752b4176fb22b35d37a71add8671576c33

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyhomedot-1.7.0-py3-none-any.whl
  • Upload date:
  • Size: 12.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","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.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6a18d50152d4aa9097d63725c21a5cd1b7deab1c4332b9d2d35fe1f8dd1b75f0
MD5 438e306f6a8c7b9f4603eb225563db94
BLAKE2b-256 262c75b03ce8083b999a9484fa6401dfe4dcf30aae8a248f94bc8ba389a63676

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