Skip to content

Module-level __getattr__ infinitely recurses on unresolved names #18

@nvzoll

Description

@nvzoll

dataforseo_client/__init__.py recurses infinitely for any attribute name it can't resolve to a submodule.

Because the fallback calls getattr() on the package itself, a missing name re-enters __getattr__ with the same name, looping until stack overflows (which initially looks like indefinite hang of a python program).

Repro:

import dataforseo_client

# Any of these hang and eventually segfault instead of resolving / raising AttributeError:
print(dataforseo_client.__version__)
print(getattr(dataforseo_client, "DoesNotExist", None))   # expected: returns None
print(hasattr(dataforseo_client, "DoesNotExist"))          # expected: returns False

print("ok")

Real life example:

import dataforseo_client          # SDK in sys.modules
import logfire
logfire.configure()               # scans sys.modules for versions -- hangs

Suspected root cause:

# dataforseo_client/__init__.py
def __getattr__(name):
      file_name = camel_to_snake(name)
      modules_to_try = ['dataforseo_client', 'dataforseo_client.api', 'dataforseo_client.models']
      for module in modules_to_try:
          try:
              imported_module = importlib.import_module(f'{module}.{file_name}')
              model = getattr(imported_module, name)
              globals()[name] = model
              return model
          except:
              try:
                  imported_module = importlib.import_module(f'{module}')   # == this package when module ==  'dataforseo_client'
                  model = getattr(imported_module, name)                   # getattr(self_package, missing_name) -- __getattr__(name) again
                  globals()[name] = model
                  return model
              except:
                  continue
      raise ImportError(f"Cannot find {name} in any of the specified modules")

When module == 'dataforseo_client', import_module('dataforseo_client') returns this same package, so getattr(imported_module, name) for an unresolved name re-invokes the module-level __getattr__(name) = recursion. Deep recursion overflows the stack rather than raising RecursionError.

Two secondary problems:

  • __getattr__ should raise AttributeError, not ImportError
  • Clauses swallow KeyboardInterrupt, SystemExit, and RecursionError

Impact

Any environment that imports dataforseo-client alongside a library that introspects modules (observability tooling, type/validation libraries, IDE debuggers) can hang or crash with no actionable error.

Workarounds

  • monkeypatching __getattr__
  • Ensuring module-scanning tools run before importing the SDK

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions