Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Strict interfaces and dep management for Python, written in Rust (github.com/gauge-sh)
68 points by the1024 24 days ago | hide | past | favorite | 12 comments
Tach is a pip package that lets you define module boundaries, automatically detect all cross-module dependencies, and then validate and enforce those dependencies.

It also supports strict interfaces for modules by overloading `__all__`.

The core static analysis is done in Rust, so all Tach commands run quickly.

The goal of Tach is to help eng teams maintain velocity while scaling quickly, something we've seen break down a number of times. Give it a try!




Looks interesting, but I'm having a hard time understanding what this does that isn't covered by the ruff rules checking for uses of private functions or classes.

It seems you can define an extra level of who's allowed to import what...but why would I want to add that on top rather than using standard conventions? Why would I want to let foo import baz, but not let bar import baz?


(Not the author)

In large codebases you can have a problem where people randomly import things from different locations leading to a host of issues including circular import issues.

For example someone finds a convenient helper function in "my_thing.foo.bar.helpers" and imports it but you don't want that dependency between those modules and the helper function was not intended to be used outside of that module.

In Python this problem is especially severe because there is no native module encapsulation mechanism other than a weak conventio of using the underscore prefix.

A tool like this helps you enforce "intentional" module boundaries so modules can't randomly reach into each other to import things. You will be forced to consider an intentional modular design where the helpers that are needed by many things are separated out and other things are allowed to import from it.


Underscore prefix is slightly more than convention: even without an explicit `__all__`, a wildcard import excludes 'underscore-private'.

IMO it's unfortunate, especially because of that behaviour, that they're importable (explicitly) at all.


Yes but it's not enough.

Especially in a large codebase where lots of different modules are technically in the same package as each other.

It's easier when a library author is publishing a small package for external consumption by other parties.

Another issue is that in Python any name in a module is implicitly available to be imported from other places.

For example let's say you have "A.py" inside of "A.py" you have "from something import foo". Now "B.py" can do something like "from A import foo".

So now "B" has a dependency that nobody really wanted to create.

Later "foo" may not be needed in "A.py" anymore and it will look like a totally unused import to anyone. Except it is being used by "B.py" too behind your back.

Someone deletes the "unused import" and something else somewhere else breaks because it can no longer import "foo" from "A".

(I have seen variations of this issue happen many many times so don't think I'm making some theoretical scenario.)


It's a very contrived scenario, and practically never happens if you use a python IDE. I've worked on multi-million line python codebases and this kind of stuff only happened when the (junior) dev insisted on using something like VSCode and not bothering to do a casual check of where they're importing something from. Also happened when you got "senior" devs with the idea of "python is just a silly little scripting language, I can pick it up in a weekend", but even then they were stubborn and didn't use a Python IDE, instead resorting to using a barebone and barely-configured installation of VSCode.

I'm not sure what use-case this serves, as all my library "don't be naughty" needs are handled by my IDE.


I agree, I just meant given it's not mere community convention, it does have some meaning in the language, it's a shame it doesn't have more.


Yes, good call out - I actually wrote a bit more about this and how we arrived at our solution with `tach`.

[0] https://www.gauge.sh/blog/the-trouble-with-all


and a lot of people stopped using them _HelperClass just doesn't look nice ....


(author here)

borplk hit this right on the head - the public/private distinction not sufficiently nuanced enough to handle the requirements of more complex codebases.

To put your example in other words, I simply want baz scoped to foo. baz may be public for all members and sub-modules of foo, but external uses of baz outside of foo would create brittle and unwanted dependencies across my codebase.


Some related work from the same people, around how they sped up their AST analysis [0]. It looks like they have a few tools that would be very useful for wrangling existing python codebases and getting them under control.

[0] https://www.gauge.sh/blog/python-extensions-should-be-lazy


In TypeScript land, module boundaries currently enforceable using eslint with nx rules: https://nx.dev/nx-api/eslint-plugin/documents/enforce-module...


Yet another tool that makes it harder to sync my projects and do ad-hoc coding on my phone, or anywhere else that I can't setup the Rust toolchain. That's all I'm seeing with this rustification of the Python ecosystem: a reduction in the breadth of practically supported targets for the language.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: