I didn't see any docs regarding dependency cycles....
# general
b
I didn't see any docs regarding dependency cycles. I thought they were kinda kosher, but I'm seeing
CycleException
(only if I use explicit
python_source
though).
Copy code
a.py depends on b.py depends (for type-checking) on a.py
Copy code
BUILD.pants

# no error
python_sources()  
# Error
python_source(name="a", source="a.py")
python_source(name="b", source="b.py")
# Also no error
python_sources(name="a", sources=["a.py"])
python_sources(name="b", sources=["b.py"])
This seems... unexpected
1
f
Why? Even without Pants, Python cannot handle import cycles.
The JVM backends tolerate dependency cycles by using
CoarsenedTarget
. But JVM compilers can handle cycles when given all of the files in the cycle.
b
But Pants CAN handle it in case 1 and 3? Also Python can handle it, the import is hidden behind
if TYPE_CHECKING
, which in this case it is. (or any other dynamic/delayed importing)
f
But I don’t recall that dependency inference handles
if
clauses.
What does dependency inference do in the
python_sources
case?
b
Dependency inference for Python just scans for
import
statements at any level in any part of the code. I think you're missing the forest for the trees though. How come when I use a generator there's no error but when not it errors. Seems like an annoying gotcha.
f
That’s why I asked:
What does dependency inference do in the
python_sources
case?
Using a generator doesn’t imply that the generated targets have dependencies on each other unless the backend opts in to that behavior.
b
./pants peek a.py b.py
Copy code
[
  {
    "address": "dirname/a.py:dirname",
    "target_type": "python_source",
    "dependencies": [
      "dirname/b.py:dirname"
    ],
    "dependencies_raw": null,
    "description": null,
    "interpreter_constraints": null,
    "resolve": null,
    "skip_black": false,
    "skip_flake8": false,
    "skip_isort": false,
    "skip_mypy": false,
    "skip_pylint": false,
    "source_raw": "a.py",
    "sources": [
      "dirname/a.py"
    ],
    "tags": null
  },
  {
    "address": "dirname/b.py:dirname",
    "target_type": "python_source",
    "dependencies": [
      "dirname/a.py:dirname"
    ],
    "dependencies_raw": null,
    "description": null,
    "interpreter_constraints": null,
    "resolve": null,
    "skip_black": false,
    "skip_flake8": false,
    "skip_isort": false,
    "skip_mypy": false,
    "skip_pylint": false,
    "source_raw": "b.py",
    "sources": [
      "dirname/b.py"
    ],
    "tags": null
  }
]
b
I know the circular import "works" with the generator, because I use it today, and
mypy
doesn't complain on missing import. But for organizational reasons I have to switch to non-generator targets. So this is surprising.
I've talked with @hundreds-father-404 about this and they pointed it it's important for this to "work" specifically for type checking. (Pants' codebases has instances of this)
f
then I don’t know. @hundreds-father-404 knows way more about this detail of the Python backend than I do
when does the cycle exception trigger?
seems like it did not trigger for
./pants peek
?
b
lint
, which I assume is where Pants starts grabbing transitive deps.
(I think it would fire on
peek
as well)
f
maybe the
lint
rule should use
CoarsenedTarget
?
b
I think it's more likely a bug in the detection code which is shadowed by using a generator?
f
and ensure that `CoarsenedTarget`’s end up in the same partiition
given the difference in behavior you’ve identified differs depending on whether you use a generator or not, that distinction in lint stands out a bit
b
I think
lint
might be a red herring here
I stand corrected.
peek
works 😮
Although,
peek
might not care about cycles. It just reports what's there.
OK
test
errors as well.
lint
is a red herring 🟥 🐟
Yup. When I use
python_sources
is_file_target
is
False
, but with
python_source
its
True
w
b
Well... poop
In a plugin, could I expose a
python_source
equivalent target which acts like it was "generated"?
w
yes…
python_sources
=P
b
For reasons I don't want to go into it's muuuuuch easier to use one-target-per-source for now
Using
python_sources
with one source still makes the target address include the filename which isn't ideal in my constrained ecosystem.
w
regarding that issue though: haven’t thought about it in a long while, but it’s entirely possible that we should remove the restriction entirely. we had it in the first place because i was squeamish about cycles when we first began to allow them. but explicitly specified dependencies are so rare, that it’s not clear it’s adding anything
2
b
.... Wait, I have a workaround
So for me
w
the target address include the filename
the
Address
type is what triggers the behavior, so you probably can’t get both the cycle behavior and address the way you want.
@bitter-ability-32190: would you mind commenting on the ticket indicating that it made something unnecessarily hard? i’ll probably follow up to suggest moving to a global cycle setting or something.
2
b
done!
h
I agree w/ adding an option re cycle tolerance. Some people may want to universally enforce no cycles
w
…or maybe a cycle-check linting backend (disabled by default), with a plugin field to allow it per-target.
1
h
so you have an escape hatch