| :::{default-domain} bzl |
| ::: |
| |
| # Circular dependencies |
| |
| Sometimes PyPI packages contain dependency cycles. For instance, a particular |
| version of `sphinx` (this is no longer the case in the latest version as of |
| 2024-06-02) depends on `sphinxcontrib-serializinghtml`. When using them as |
| `requirement()`s, ala |
| |
| ```starlark |
| py_binary( |
| name = "doctool", |
| ... |
| deps = [ |
| requirement("sphinx"), |
| ], |
| ) |
| ``` |
| |
| Bazel will protest because it doesn't support cycles in the build graph -- |
| |
| ``` |
| ERROR: .../external/pypi_sphinxcontrib_serializinghtml/BUILD.bazel:44:6: in alias rule @pypi_sphinxcontrib_serializinghtml//:pkg: cycle in dependency graph: |
| //:doctool (...) |
| @pypi//sphinxcontrib_serializinghtml:pkg (...) |
| .-> @pypi_sphinxcontrib_serializinghtml//:pkg (...) |
| | @pypi_sphinxcontrib_serializinghtml//:_pkg (...) |
| | @pypi_sphinx//:pkg (...) |
| | @pypi_sphinx//:_pkg (...) |
| `-- @pypi_sphinxcontrib_serializinghtml//:pkg (...) |
| ``` |
| |
| The `experimental_requirement_cycles` attribute allows you to work around these |
| issues by specifying groups of packages which form cycles. `pip_parse` will |
| transparently fix the cycles for you and provide the cyclic dependencies |
| simultaneously. |
| |
| ```starlark |
| ... |
| experimental_requirement_cycles = { |
| "sphinx": [ |
| "sphinx", |
| "sphinxcontrib-serializinghtml", |
| ] |
| }, |
| ) |
| ``` |
| |
| `pip_parse` supports fixing multiple cycles simultaneously, however, cycles must |
| be distinct. `apache-airflow`, for instance, has dependency cycles with a number |
| of its optional dependencies, which means those optional dependencies must all |
| be a part of the `airflow` cycle. For instance: |
| |
| ```starlark |
| ... |
| experimental_requirement_cycles = { |
| "airflow": [ |
| "apache-airflow", |
| "apache-airflow-providers-common-sql", |
| "apache-airflow-providers-postgres", |
| "apache-airflow-providers-sqlite", |
| ] |
| } |
| ) |
| ``` |
| |
| Alternatively, one could resolve the cycle by removing one leg of it. |
| |
| For example, while `apache-airflow-providers-sqlite` is "baked into" the Airflow |
| package, `apache-airflow-providers-postgres` is not and is an optional feature. |
| Rather than listing `apache-airflow[postgres]` in your `requirements.txt`, which |
| would expose a cycle via the extra, one could either _manually_ depend on |
| `apache-airflow` and `apache-airflow-providers-postgres` separately as |
| requirements. Bazel rules which need only `apache-airflow` can take it as a |
| dependency, and rules which explicitly want to mix in |
| `apache-airflow-providers-postgres` now can. |
| |
| Alternatively, one could use `rules_python`'s patching features to remove one |
| leg of the dependency manually, for instance, by making |
| `apache-airflow-providers-postgres` not explicitly depend on `apache-airflow` or |
| perhaps `apache-airflow-providers-common-sql`. |