fierce-fall-70380
02/07/2025, 2:44 PMdb package containing over 100 submodules providing DB access to various features. The foo/db/__init__.py file implicitly imports all 100+ submodules (whether the consumer uses them or not).
A super common pattern in the code base is to do:
from foo import db
... and then access submodules like: db.bar.do_something() without actually importing db.bar explicitly.
Unfortunately, that means that if we're calculating which tests to run and execute an inquiry like pants dependents --dependents-transitive lib-python/foo/db/some_feature.py, any test or test script testing code that uses foo.db is going to be in the results list whether it uses some_feature or not.
My first thought is that we could refactor foo/db/__init__.py to implement lazy loading (per PEP 562), but I believe that just creates the opposite problem (instead of creating a transitive relationship for all modules, changing the foo.db imports to be programmatic means Pants won't find any).
So I'm left wondering whether the only real solution is to refactor every place where we do from foo import db and update them to explicitly import only the modules it needs. I just did a grep though and there are over 1000 (!) modules where we use this pattern of implicit import. My best idea so far is to enlist an LLM to help me write a script that can automate that refactor. But maybe there are other tricks I don't know about that could solve for this??fierce-fall-70380
02/07/2025, 2:59 PMif we're calculating which tests to run and execute an inquiry likeSide note: I believe
pants test is supposed to automate this part for you. I'm not sure if we're going to be able to use that right away, but I'm assuming it uses the same dependency graphs under the hood and would run into the same issue ...wide-midnight-78598
02/07/2025, 3:02 PMwide-midnight-78598
02/07/2025, 3:02 PMelegant-florist-94385
02/07/2025, 3:04 PMimport db and then db.bar.do_something(), this has effectively created an undocumented public interface. the calling module is essentially inspecting the internals of another object. (eg. see the Law of Demeter ). It is generally preferable to be explicit about everything dependencies, imports, etc.) even if it costs a few extra lines of code.
From a pants perspective, all dependencies are on a file to file basis, so all that's happening in your case is seeing import db and inferring db/__init__.py as a dependency, then reading db/__init__.py , seeing lots of imports, and inferring everything else.fierce-fall-70380
02/07/2025, 3:05 PMwide-midnight-78598
02/07/2025, 3:05 PMfierce-fall-70380
02/07/2025, 3:07 PMfierce-fall-70380
02/07/2025, 3:08 PMAlso, what's your test command?@wide-midnight-78598 We use pytest with a few layers of custom tooling on top.
wide-midnight-78598
02/07/2025, 3:09 PMpants test :: ? pants --changed-since=origin/main --changed-dependents=transitive test ?
https://www.pantsbuild.org/stable/docs/using-pants/advanced-target-selectionfierce-fall-70380
02/07/2025, 3:11 PMpants {dependencies,dependents,paths} to familiarize with its capabilities and figure out what changes we'll need to make to take advantage.fierce-fall-70380
02/07/2025, 3:12 PMpants test eventually, we may need to use the lower-level tools for now. We currently use a bunch of custom tooling to shard tests across different gitlab jobs (for parallelization etc).wide-midnight-78598
02/07/2025, 3:12 PMfierce-fall-70380
02/07/2025, 3:12 PMwide-midnight-78598
02/07/2025, 3:13 PMto shard testshttps://www.pantsbuild.org/stable/docs/using-pants/advanced-target-selection#sharding-the-input-targets Lots of builtins to help strip out some customization. However, as you're already doing - cleaning up whatever tech debt you can is always a good first step
fierce-fall-70380
02/07/2025, 3:14 PMfierce-fall-70380
02/07/2025, 3:15 PMwide-midnight-78598
02/07/2025, 3:17 PMimport ast in the pants codebase, there are a few simple ones. We also have a few complicated ones that I probably wouldn't use as an example, since they're such odd, niche cases.
Given what you're doing, arguably, you could use sed, awk, and grep - but they miss that little bit of context - and that's where those scripts fall over or get more complicated (e.g. now you have to account for multi line comments or whatever)wide-midnight-78598
02/07/2025, 3:19 PMGet calls to the method call - so, it's messy, but the job itself is complicated
https://github.com/pantsbuild/pants/blob/main/src/python/pants/util/cstutil.py
https://github.com/pantsbuild/pants/blob/main/src/python/pants/goal/migrate_call_by_name.pywide-midnight-78598
02/07/2025, 3:19 PMfierce-fall-70380
02/07/2025, 3:19 PMfoo library used to be an absolute kitchen sink. A few years ago I started to chop it up -- we have a foo-core now with the lowest level facilities -- and I mostly used sed/awk/grep to update the imports as you mentioned. 🙂 (Actually I have gsed installed on my Mac because it has a better -i function for in-place updates.)fierce-fall-70380
02/07/2025, 9:26 PMfierce-fall-70380
02/07/2025, 9:26 PMfierce-fall-70380
02/07/2025, 9:26 PMfierce-fall-70380
02/07/2025, 9:28 PMtest_*.py, the rest was the actual implementation.wide-midnight-78598
02/07/2025, 9:52 PM