Summary
Bug Report: The mergepythonclient library (version 2.3.1) contains a critical flaw in its dynamic import system that causes infinite recursion when tools like freezegun attempt to introspect module attributes. This results in a RecursionError: maximum recursion depth exceeded and prevents the library
from being used in testing environments that rely on module introspection.
Environment
- Package: mergepythonclient version 2.3.1
- Python: 3.13.2 (also affects other Python versions)
- Platform: All platforms (tested on macOS Darwin 24.6.0)
- Trigger: Module introspection tools (e.g., freezegun, potentially others)
Root Cause
The issue is located in /merge/resources/init.py where the _dynamic_imports dictionary contains self-referential entries:
_dynamic_imports: typing.Dict[str, str] = {
"accounting": ".", # ← Problematic: "." refers to the same module
"ats": ".",
"crm": ".",
"filestorage": ".",
"hris": ".",
"ticketing": ".",
}
When the getattr method is triggered:
- import_module(".", package) imports the same module (merge.resources)
- getattr(module, attr_name) attempts to access the attribute on the same module
- This triggers getattr again → infinite recursion
Reproduction Steps
"""
Reproduces the RecursionError in mergepythonclient 2.3.1
Save as reproduce_bug.py and run: python reproduce_bug.py
"""
import sys
import traceback
def test_recursion_bug():
try:
from freezegun import freeze_time
@freeze_time("2025-01-01T00:00:00")
def dummy_test():
return "success"
result = dummy_test()
print("✅ Test passed:", result)
except RecursionError as e:
print("❌ RecursionError occurred:")
print(f"Error: {str(e)}")
tb = traceback.format_exc()
print(tb)
return False
except Exception as e:
print(f"❌ Other error: {e}")
return False
return True
if __name__ == "__main__":
print("Testing mergepythonclient recursion bug...")
print(f"Python version: {sys.version}")
# Check if merge is installed
try:
import merge
print(f"Merge version: {getattr(merge, '__version__', 'unknown')}")
except ImportError:
print(
"❌ mergepythonclient not installed. Install with: pip install mergepythonclient"
)
sys.exit(1)
success = test_recursion_bug()
if not success:
print("\n🐛 Bug confirmed: RecursionError in mergepythonclient")
sys.exit(1)
Stack trace
Traceback (most recent call last):
File "/Users/user/devel/bug/test.py", line 18, in test_recursion_bug
result = dummy_test()
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/freezegun/api.py", line 901, in wrapper
with self as time_factory:
^^^^
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/freezegun/api.py", line 717, in __enter__
return self.start()
~~~~~~~~~~^^
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/freezegun/api.py", line 806, in start
module_attrs = _get_cached_module_attributes(module)
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/freezegun/api.py", line 144, in _get_cached_module_attributes
_setup_module_cache(module)
~~~~~~~~~~~~~~~~~~~^^^^^^^^
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/freezegun/api.py", line 123, in _setup_module_cache
all_module_attributes = _get_module_attributes(module)
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/freezegun/api.py", line 112, in _get_module_attributes
attribute_value = getattr(module, attribute_name)
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/merge/__init__.py", line 33, in __getattr__
result = getattr(module, attr_name)
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/merge/resources/__init__.py", line 26, in __getattr__
result = getattr(module, attr_name)
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/merge/resources/__init__.py", line 26, in __getattr__
result = getattr(module, attr_name)
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/merge/resources/__init__.py", line 26, in __getattr__
result = getattr(module, attr_name)
[Previous line repeated 985 more times]
File "/Users/user/devel/bug/.venv/lib/python3.13/site-packages/merge/resources/__init__.py", line 25, in __getattr__
module = import_module(module_name, __package__)
File "/Users/user/.local/share/mise/installs/python/3.13.2/lib/python3.13/importlib/__init__.py", line 88, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1384, in _gcd_import
RecursionError: maximum recursion depth exceeded
Impact
- Critical: Prevents use of mergepythonclient in test environments using freezegun
- Widespread: Affects any tool that performs module introspection
Suggested Fix
Replace the self-referential "." entries in /merge/resources/init.py:
Current (Broken) Code:
_dynamic_imports: typing.Dict[str, str] = {
"accounting": ".",
"ats": ".",
"crm": ".",
"filestorage": ".",
"hris": ".",
"ticketing": ".",
}
Suggested Fix:
_dynamic_imports: typing.Dict[str, str] = {
"accounting": ".accounting",
"ats": ".ats",
"crm": ".crm",
"filestorage": ".filestorage",
"hris": ".hris",
"ticketing": ".ticketing",
}
Alternative Safer Approach:
Add recursion protection to the getattr method:
_in_getattr = set()
def __getattr__(attr_name: str) -> typing.Any:
if attr_name in _in_getattr:
raise AttributeError(f"Recursive access detected for {attr_name}")
module_name = _dynamic_imports.get(attr_name)
if module_name is None:
raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
try:
_in_getattr.add(attr_name)
module = import_module(module_name, __package__)
result = getattr(module, attr_name)
return result
except ImportError as e:
raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
except AttributeError as e:
raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
finally:
_in_getattr.discard(attr_name)
Workarounds for Users
Until fixed, users can work around this issue by configuring freezegun to ignore the merge package:
import freezegun
freezegun.configure(extend_ignore_list=['merge'])
Additional Notes
- This bug was introduced in the dynamic import system design
- The same pattern may exist in other auto-generated SDK code
- Testing with module introspection tools should be part of the CI/CD pipeline
- Consider adding unit tests that use freezegun or similar introspection tools
Repository Information
Thank you for maintaining this library! This fix would greatly improve compatibility with testing frameworks.
Summary
Bug Report: The mergepythonclient library (version 2.3.1) contains a critical flaw in its dynamic import system that causes infinite recursion when tools like freezegun attempt to introspect module attributes. This results in a RecursionError: maximum recursion depth exceeded and prevents the library
from being used in testing environments that rely on module introspection.
Environment
Root Cause
The issue is located in /merge/resources/init.py where the _dynamic_imports dictionary contains self-referential entries:
When the getattr method is triggered:
Reproduction Steps
Stack trace
Impact
Suggested Fix
Replace the self-referential "." entries in /merge/resources/init.py:
Current (Broken) Code:
Suggested Fix:
Alternative Safer Approach:
Add recursion protection to the getattr method:
Workarounds for Users
Until fixed, users can work around this issue by configuring freezegun to ignore the merge package:
Additional Notes
Repository Information
Thank you for maintaining this library! This fix would greatly improve compatibility with testing frameworks.