ansys / ansys/pydynamicreporting
Make serverless exports lazy so item-only imports do not load ADR and templates
- Dominant language
- Python
- Stars
- 12
- Forks
- 5
- Avg merge
- 3d 14h
- Merged PRs (30d)
- 9
Description
## Problem
Importing one public serverless area loads all three major areas.
For example, this item-only import also loads the ADR and template modules:
import sys
from ansys.dynamicreporting.core.serverless.item import Item
assert "ansys.dynamicreporting.core.serverless.adr" not in sys.modules
assert "ansys.dynamicreporting.core.serverless.template" not in sys.modules
Both assertions fail at commit 7d4dcf4cbfc3652f28cb9d8f053d05d1d237d038.
Python runs serverless/__init__.py before it loads serverless.item. The current initializer imports ADR first, then every item class, then every template class. This means an item-only consumer cannot avoid the ADR and template imports even when its own code uses no report templates.
This affects small adapters and type-discovery callers. It also makes downstream delayed imports less useful because the first public PyDR serverless import loads unrelated modules.
## Current cause
The public package initializer contains eager imports from these modules:
- .adr
- .item
- .template
The public names and __all__ list are useful and should stay compatible. The eager assignments are the part that needs to change.
Source:
https://github.com/ansys/pydynamicreporting/blob/7d4dcf4cbfc3652f28cb9d8f053d05d1d237d038/src/ansys/dynamicreporting/core/serverless/__init__.py
## Proposed fix
Keep the current top-level public names and __all__, but resolve each name only when it is requested. This can use the standard library and PEP 562, with no new dependency.
A small export map is enough:
from importlib import import_module
_EXPORT_MODULES = {
"ADR": ".adr",
"Item": ".item",
"Table": ".item",
"Template": ".template",
"BasicLayout": ".template",
}
__all__ = list(_EXPORT_MODULES)
def __getattr__(name):
module_name = _EXPORT_MODULES.get(name)
if module_name is None:
raise AttributeError(name)
value = getattr(import_module(module_name, __name__), name)
globals()[name] = value
return value
def __dir__():
return sorted(set(globals()) | set(__all__))
The real map would contain every name in the current __all__ list. Grouping the names by module can avoid repeating module strings.
Caching each loaded value in globals preserves normal repeated attribute access. TYPE_CHECKING-only imports can keep static analysis and generated documentation clear without loading the modules at runtime.
Accessing ADR may still load item and template modules because adr.py uses both. That is expected. Accessing an item class alone should not load ADR or templates, and accessing a template class alone should not load ADR or items.
## Compatibility
The fix should preserve these public calls:
from ansys.dynamicreporting.core.serverless import ADR
from ansys.dynamicreporting.core.serverless import Item, Table
from ansys.dynamicreporting.core.serverless import BasicLayout, Template
The order and values in __all__ should stay unchanged. Code that reads every exported attribute will still load every area because it asked for every area.
Issue #595 proposes a public item and template registry. That work is related but separate. A registry caller may need both groups; an item-only caller should not have to load both groups.
## Tests
Use clean subprocesses so a prior import cannot hide the result.
1. Import serverless.item and confirm that serverless.adr and serverless.template are absent from sys.modules.
2. Import serverless.template and confirm that serverless.adr and serverless.item are absent from sys.modules.
3. Import Item and Table from the top-level serverless package and confirm their current class identities.
4. Import Template and BasicLayout from the top-level package and confirm their current class identities.
5. Import ADR and run the existing ADR checks.
6. Compare __all__ before and after the change.
7. Confirm that an unknown package attribute raises AttributeError.
8. Build the API documentation and confirm that all public exports remain documented.
## Acceptance criteria
- Item-only imports do not load ADR or template modules.
- Template-only imports do not load ADR or item modules.
- Existing top-level public import paths keep working.
- __all__ keeps the same public names and order.
- ADR behavior is unchanged.
- The implementation adds no dependency.
- Clean-process tests cover import isolation and compatibility.
Contributor guide
Assessment
This issue has not been assessed yet.