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=Falseto 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:
- Target is already a symlink to the correct source — skipped, no action taken.
- 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. - 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:
-
CLI flag — pass
--dry-runwhen running your script:uvx --with pyhomedot python main.py --dry-run
-
Programmatically — pass
dry_run=Truetohome.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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7567a840dbc2fd79b90b239c4680c8c92d6d4aabb4134d957b05fe21725eb240
|
|
| MD5 |
f8d2e3b622ac2636107c466307c18b5f
|
|
| BLAKE2b-256 |
14bee4d44dce64bbebd2e5915a042a752b4176fb22b35d37a71add8671576c33
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a18d50152d4aa9097d63725c21a5cd1b7deab1c4332b9d2d35fe1f8dd1b75f0
|
|
| MD5 |
438e306f6a8c7b9f4603eb225563db94
|
|
| BLAKE2b-256 |
262c75b03ce8083b999a9484fa6401dfe4dcf30aae8a248f94bc8ba389a63676
|