I'm trying to create a Homebrew formula for a Python project.
Here's the Homebrew formula:
class Scanman < Formula
include Language::Python::Virtualenv
desc "Using LLMs to interact with man pages"
url "https://github.com/nikhilkmr300/scanman/archive/refs/tags/1.0.1.tar.gz"
sha256 "93658e02082e9045b8a49628e7eec2e9463cb72b0e0e9f5040ff5d69f0ba06c8"
depends_on "[email protected]"
def install
virtualenv_install_with_resources
bin.install "scanman"
end
test do
# Simply run the program
system "#{bin}/scanman"
end
end
Upon running the application with the installed scanman version, it fails to locate my custom modules housed within the src directory.
ModuleNotFoundError: No module named 'src'
Any insights into why this is happening?
Here's my directory structure if that helps:
.
├── requirements.txt
├── scanman
├── scanman.rb
├── setup.py
└── src
├── __init__.py
├── cli.py
├── commands.py
├── manpage.py
├── rag.py
└── state.py
The main executable is scanman. It's a Python script that lets you interact with man pages using an LLM.
It's worth noting the following:
- When I run the local version of
scanmanfrom my repository, it works absolutely fine. - Other 3rd party packages installed from PyPI don't throw any error. I can't find them in
/usr/local/Cellar/scanman/1.0.1/libexec/lib/python3.11/site-packages/, however.
I took a look at the repo and saw that
scanmanitself is a Python script.You actually have two problems:
packages=find_packages(where="src")insetup.py, which tells Setuptools (the program that readssetup.pyand builds your package) to install everything inside ofsrc. Therefore your modulescli.py,commands.py, etc. will all end up installed as top-level modules, which you'd access asimport cli,import commands. That's probably not what you want.#!/usr/bin/env python3is at the mercy of whatever is inPATH, which might not even be managed by Homebrew, let alone the specific venv created by Homebrew for your package.I elaborated on some more packaging issues and a possible simple resolution in the comments. If you're using an LLM for this work, use a different one, because it's giving you bad advice. You should not make a Pip-installable package called
src. You should strive to make it so that Python can find your code without any additional env vars or other messing around. You also don't want to rewrite your code. My suggestions here are meant to achieve all of those things.It looks like you are already attempting to make a Pip-installable package, so rather than messing around trying to make your current setup work, I will illustrate how to do that in a way that doesn't make a mess, and follows standard longstanding conventions.
Your file structure will look like this:
The absence of the top-level
scanmanscript is deliberate. Keep reading.And your
setup.pybecomes:Note that this is almost identical to what you had before.
Then:
src.cli,src.manpageetc. toscanman.cli,scanman.manpage.scanmanscript to a function inside the scriptsrc/scanman/__main__.py, and remove the shebang line. . Runpip install --editable .in your developer environment (ideally a venv!). This only needs to be run once, you do not need to re-run this when you update your code.Your resulting
src/scanman/__main__.pyscript will look like this:Note that you don't need both
logging.basicConfig(level=logging.ERROR)andlogger.setLevel(logging.ERROR). Log levels are hierarchical; whatever you set on the root logger (which is whatlogging.basicConfig(level=...)does) will propagate to all other loggers, unless you explicitly set a different level on those other loggers.As a test, you should be able to run both
python -m scanmanandscanmanusing your developer environment:The reason that we like the
src/layout is to hide your source code from the Python search path, so that you are 100% sure you are only running the version of your code that is installed in your venv, not whatever you happen to have in your Git repo at the time you run your code. This improves the chances that your code will work on other people's machines.Finally, I suggested moving the Homebrew formula to the
Formula/directory because that's standard in Homebrew, and it makes clear thatscanman.rbis a Homebrew formula, rather than another project script.There are also some things you did correctly in your repo that I want to call out!
First, you only set your logging config in the main script, not in the library code. That's good, because you shouldn't be trying to modify global application state from inside the library code.
It also looks like you're using
pip-compileorpip freezeto emit therequirements.txt. That's a great idea. Keep doing that! Sadly I don't know how to get Homebrew to actually install dependencies from that file, as opposed to just using your declared package deps insetup.pywith full dependency resolution. You might need to ask in the Homebrew forum for that. Ideally, Homebrew would run something like this:But that would require it to clone your repo before installing, rather than simply installing directly from Github via Pip.