Tach lets you visualize the architecture of your Python codebase, and gives you the tools to incrementally improve it. It uses module boundaries to give teams the benefits of microservices without the deployment complexity.
If your code has been getting tangled up as your team and codebase grows, Tach helps you move back in the right direction, incrementally and quickly. You can use Tach to incrementally adopt a "modular monolith" architecture [1], for better local reasoning and smoother feature development.
Since our last Show HN (https://news.ycombinator.com/item?id=41359181) we've shipped support for layers, third party dependencies, visualizations, and more.
Tach is: * Open source (MIT) * completely free * fast (written in Rust) * in use by teams at NVIDIA, PostHog, and more.
One way Tach differs from existing systems that handle this problem (build systems, import linters, etc) is the ability to be incrementally adopted. Also, runtime speed.
If you struggle with dependencies, onboarding new engineers, or a massive codebase, Tach is for you! We built it with developers in mind - with clean integrations into Git, CI/CD, and IDEs, and the performance for it to be effective in any form factor.
[1] https://www.milanjovanovic.tech/blog/what-is-a-modular-monol...
> Note that this graph is generated remotely with the contents of your `tach.toml`
Isn't shipping off parts of your codebase to a 3rd party without warning in the CLI a security risk? Or in regulatory environments you get audited that your code was only stored on properly vetted services which is why some sales cycles for AI coding assistant tools are so long. It would be kind of frustrating to have something like that happen and get set back on licensing, etc.
Just from the video it doesn't seem like any sort of warning that you are shipping config files to your servers and the URL that you produced doesn't seem to have any authentication.
Maybe i am misunderstanding that functionality, but it gives me pause to use it.
In short, we want to make the visualization UX as smooth as possible, and this is best done with a web app. The URLs use UUIDs, and the contents being sent don't include literal source code, only module names and Tach configuration. We will also delete graphs by UUID on request, and have done so in the past.
That said, we do try to be up-front about this, which is why that disclaimer exists, and when running this command on the CLI, you must supply an explicit `--web` argument to `tach show`. Otherwise, the default behavior is to generate a GraphViz DOT file locally.
I’m mostly kidding but incidentally PHART was born in order to visualize Python dependency graphs in-line in 7-bit ASCII because I wanted the functionality in my dependency analyzing code summarizing concatenator tool I was using to aid in pair-programming with ChatGPT and Claude when codebases started outgrowing useful context lengths. That tool is here https://github.com/scottvr/chimeracat/ (it is nowhere as slick-looking as OP’s app, but also that is by design.)
The first time someone in public said they were curious to see the chimeracat output for his company’s codebase was also the first time I considered “wow.. how do I make sure people know they can trust chimeracat isn’t stealing their code?” and started thinking of ways to give people that surety and safety for any app, because so realized that though it was my first time to think about how “code analysis” tools like this, it even linters, prettifier’s etc. are a fertile ground for subterfuge and espionage, it was no doubt not the first time the thought had occurred elsewhere, and likely to at least a handful of folks who would (and no doubt are) putting such tools out there in the wild.
still doesn't explain why you need to ship the data to a third party
> and this is best done with a web app
debatable. you could always write a GUI app. it's not that hard for such a self-contained project
there would be _a lot_ to gain from having this run totally locally without any network access and leaking source code to third parties.
Also, the mere fact that it sends any data, no matter what you say it contains is a non-starter at many places. And even module names can contain proprietary data.
So I'm really happy to see a project like this. Well done. Can't wait to see more
uv tool install tach
rather than pip install tach
that way tach is installed system-wide but in its own isolated venvA seasoned Python developer will rarely, if ever, directly pip install something. Instead they would manually add it to pyproject.toml or, if they use something like poetry, use that to add it, or they'd use something like pipx to install it as a "global" tool (or just their system package manager). This has been true for years now. I think it's time projects stop writing "pip install x" and we come up with a standard way to say "the package name is x" and maybe a recommended installation method (like use uv/pipx to install as a system tool, or add to your project dependencies etc).
Python is really great for quickly developing applications.
However, maintaining them is a real pain point—especially when it comes to packages and their dependencies.
Furthermore, because there isn’t a compile-time checker, function or method signatures can change unnoticed. Compilers are great for catching such issues at compile time rather than at runtime. Python does have mypy, which can play that role, but the package must support it. Currently, you are dependent on the package maintainer regarding their adherence to semver.
Maybe this project will be able to fill that hole.
What I wanted was to work at a coarser package level. For example if you have the modules `foo.a`, `foo.b`, `bar.a`, and `bar.b`, I'd like a rule that `bar` can import from `foo` but not vice versa, without having to list or care about the submodules.
Is that something you'd want to support?
```
[[modules]]
path = "foo"
depends_on = []
[[modules]]
path = "bar"
depends_on = ["foo"]
```
would do the trick, assuming both are within a configured source root, and their children are _not_ also marked as modules. If the children are marked as modules, their dependency rules are treated separately and wouldn't automatically inherit from a parent.
We'll add this to the README.
I liked the video-example, it's way better than examples in many projects that use just text, when the tool does something quite complex that better be demonstrated in a video with a narrator explaining what's happening and why.
Tach is also more opinionated - so it doesn't require you to write any custom code, and uses declarative config to enforce your desired architecture.
We're always happy to chat about adding more design partners! email: founders@gauge.sh
We have well established conventions like prefixing private modules and symbols with an underscore, or declaring your public interfaces in the __init__.py file, but the Python developer decries it as "busywork", "weird" and "hard to read", so we instead use tools like this.
We can manage dependencies with protocols, a type checker and generally following SOLID principles, but the Python developer decries it as "too indirect and convoluted", so we instead use tools like this.
This is more commentary on the Python developer than this tool. Tach looks great.
Part of the philosophy here is that the tools and techniques you're describing can (and should) be used diligently to solve this problem, and Tach is often a complement to this approach.
The benefit of centralizing the concern into a single tool, and often a single config file, is that teams get better documentation, earlier feedback (in-editor vs. code review), and more visibility when planning new development. Teams also get to choose _how_ they would like to satisfy Tach's config, and other teams can still rely on the same guarantees due to Tach's static checks.
The language doesn't enforce them, so they may as well not exist. See: python dependency management.
> This is more commentary on the Python developer than this tool.
100%. Python has become an unstructured Wild West, perhaps even worse than modern JavaScript. The "Zen of Python" is a bold faced lie.
Python has incredible use cases. It blends together different disciplines effectively. But perhaps we should ask ourselves whether or not it's a language suitable for writing large monoliths in.
I see your point. You can enforce them with mypy by declaring your exports in your __init__.py file, using the `as` aliasing method or using `__all__`: https://mypy.readthedocs.io/en/stable/command_line.html#cmdo....
Here is an example project that is configured as if it were a uv workspace: https://github.com/gauge-sh/tach/tree/main/python/tests/exam...
In that project, `tach check-external` would handle between-workspace dependencies, while the core `modules` and `interfaces` config would handle within-workspace dependencies.
Soon these will be better unified, we kept the 1st-party/3rd-party distinction separate while we learned what the UX should be.
Is it “kay-leen” or “kay-lee-an”