Skip to content

Commit ad8057c

Browse files
fix(iast): fix import loop [backport 2.20] (#12129)
Backport 03e8375 from #12095 to 2.20. The usage of `callonce` in this function (that is now used in IAST and SCA) was triggering an import loop: ``` Traceback (most recent call last): File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/utils/cache.py", line 124, in _ retval, exc = f.__callonce_result__ # type: ignore[attr-defined] ^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'function' object has no attribute '__callonce_result__' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/telemetry/writer.py", line 622, in periodic self._app_dependencies_loaded_event(newly_imported_deps) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/telemetry/writer.py", line 420, in _app_dependencies_loaded_event packages = update_imported_dependencies(self._imported_dependencies, newly_imported_deps) File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/telemetry/data.py", line 76, in update_imported_dependencies dists = get_module_distribution_versions(module_name) File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/packages.py", line 72, in get_module_distribution_versions pkgs = get_package_distributions() File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/utils/cache.py", line 135, in _ raise exc File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/module.py", line 252, in _find_first_hook callable(cond) and cond(module.__name__) ~~~~^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.13/site-packages/ddtrace/appsec/_iast/_ast/ast_patching.py", line 486, in _should_iast_patch if _is_first_party(module_name): ~~~~~~~~~~~~~~~^^^^^^^^^^^^^ File "/usr/local/lib/python3.13/site-packages/ddtrace/appsec/_iast/_ast/ast_patching.py", line 467, in _is_first_party _IMPORTLIB_PACKAGES = set(get_package_distributions()) ~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/utils/cache.py", line 135, in _ raise exc File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/utils/cache.py", line 127, in _ retval = f() File "/usr/local/lib/python3.13/site-packages/ddtrace/internal/packages.py", line 58, in get_package_distributions return importlib_metadata.packages_distributions() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/local/lib/python3.13/importlib/metadata/__init__.py", line 1045, in packages_distributions for pkg in _top_level_declared(dist) or _top_level_inferred(dist): ~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/local/lib/python3.13/importlib/metadata/__init__.py", line 1088, in _top_level_inferred opt_names = set(map(_get_toplevel_name, always_iterable(dist.files))) ^^^^^^^^^^ File "/usr/local/lib/python3.13/importlib/metadata/__init__.py", line 534, in files make_files( ~~~~~~~~~~^ self._read_files_distinfo() ^^^^^^^^^^^^^^^^^^^^^^^^^^^ or self._read_files_egginfo_installed() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ or self._read_files_egginfo_sources() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/usr/local/lib/python3.13/importlib/metadata/_functools.py", line 102, in wrapper return func(param, *args, **kwargs) File "/usr/local/lib/python3.13/importlib/metadata/__init__.py", line 527, in make_files return starmap(make_file, csv.reader(lines)) ^^^^^^^^^^ AttributeError: partially initialized module 'csv' from '/usr/local/lib/python3.13/csv.py' has no attribute 'reader' (most likely due to a circular import) ``` APPSEC-56526 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Federico Mon <[email protected]>
1 parent 57910a7 commit ad8057c

File tree

3 files changed

+6
-3
lines changed

3 files changed

+6
-3
lines changed

ddtrace/appsec/_iast/_ast/ast_patching.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ddtrace.appsec._python_info.stdlib import _stdlib_for_python_version
1818
from ddtrace.internal.logger import get_logger
1919
from ddtrace.internal.module import origin
20+
from ddtrace.internal.packages import get_package_distributions
2021
from ddtrace.internal.utils.formats import asbool
2122

2223
from .visitor import AstVisitor
@@ -461,8 +462,6 @@ def _is_first_party(module_name: str):
461462
return False
462463

463464
if not _IMPORTLIB_PACKAGES:
464-
from ddtrace.internal.packages import get_package_distributions
465-
466465
_IMPORTLIB_PACKAGES = set(get_package_distributions())
467466

468467
return module_name.split(".")[0] not in _IMPORTLIB_PACKAGES

ddtrace/internal/packages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def get_distributions():
4545
return pkgs
4646

4747

48-
@callonce
48+
@cached(maxsize=1)
4949
def get_package_distributions() -> t.Mapping[str, t.List[str]]:
5050
"""a mapping of importable package names to their distribution name(s)"""
5151
try:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
Code security (IAST): This fix resolves an issue where the usage of `callonce` decorator could trigger an import loop

0 commit comments

Comments
 (0)