[bot] Merge master/b1437c08 into rel/dev - #1817
Merged
Merged
Conversation
`import gooddata_sdk` pulled in 2333 modules and ~0.58s on a warm cache, because
three separate layers imported everything available rather than what was needed.
A consumer that wants a single class (e.g. `Visualization`) paid the whole bill.
1. `gooddata_sdk/__init__.py` eagerly re-exported every public name, so touching
any leaf module ran the entire SDK's import graph first. The re-exports are now
resolved lazily via PEP 562 `__getattr__` (the pattern scipy/sklearn use), with
`__all__` for `import *` and a `TYPE_CHECKING` block so type checkers and the
griffe-based docs builder still resolve every name statically.
2. Five modules did `import gooddata_api_client.models as afm_models`, and
`execution.py` did `from gooddata_api_client import models`. That aggregate
imports all 1402 generated model classes; the SDK references 58. The generated
header of that file says as much ("import only the models that you directly
need"), and it is safe because openapi-generator emits a per-model
`lazy_import()`, so leaf imports do not cascade. The 58 now live in
`gooddata_sdk/_models.py`, which keeps the `afm_models.X` spelling at the call
sites and gives a single list to maintain.
3. `client.py` and `catalog_service_base.py` did `from gooddata_api_client import
apis`, importing all ~130 generated API classes (and transitively most models)
to instantiate six. Those six now come from `gooddata_sdk/_apis.py`.
Measured on py3.14, warm .pyc, best of 7 with the two versions interleaved:
import gooddata_sdk 0.58s / 2333 mods -> 0.04s / 160 mods
from gooddata_sdk.visualization import
Visualization 0.54s / 2333 mods -> 0.19s / 991 mods
from gooddata_sdk import GoodDataSdk 0.65s / 2333 mods -> 0.33s / 1402 mods
The module counts are the stable figure; the wall times are this machine and move
with load, so they are interleaved rather than compared across runs.
Keeping attribute access working
--------------------------------
The eager `__init__` used to populate the whole module tree as a side effect, so
`import gooddata_sdk` then `gooddata_sdk.catalog.workspace` resolved. Dropping it
broke that, so every subpackage now gets a lazy submodule `__getattr__` built by
`gooddata_sdk/_lazy.py`. It deliberately does not translate a `ModuleNotFoundError`
raised *inside* an existing submodule into `AttributeError`, so a genuinely missing
dependency still reports itself rather than looking like a typo.
Two intentional differences remain. `dir(gooddata_sdk)` now lists the public API
plus every submodule (previously only the ones that happened to be imported), and
`from gooddata_sdk import *` no longer leaks the `logging` module or the submodule
names, since `__all__` now states the public API explicitly.
Maintaining the lazy-import tables
----------------------------------
PEP 562 needs the name -> module mapping as data, and hand-maintaining ~260 string
pairs would drift from the real imports invisibly: a missing entry means
`from gooddata_sdk import NewThing` raises AttributeError at runtime, and a stale
module path is just a wrong string.
So the `TYPE_CHECKING` block is the single source of truth. It holds ordinary
`from x import Y` statements that IDEs autocomplete, refactorings rewrite and
`ty`/pyright verify, and `scripts/sync_lazy_imports.py` derives `_LAZY_IMPORTS` and
`__all__` from it by AST into a marker-delimited region that is never edited by
hand. Adding a public export is one normal import plus `--update`.
Drift is caught in two independent places, verified against three scenarios (export
added, module renamed, export removed):
- the script with no flags reports drift and exits non-zero, and
`test_lazy_imports_map_matches_type_checking_block` runs it, so CI fails with the
command to fix it;
- `test_all_lazy_exports_resolve` covers the other half -- a module that moved
without the `TYPE_CHECKING` block being updated.
The rest of `tests/sdk/test_lazy_imports.py` guards the module savings themselves,
since one convenience import silently undoes them. The attribute-access cases each
run in their own interpreter: a missing lazy hook in a subpackage is masked as soon
as anything else has imported the tree.
gooddata-pandas used the models aggregate for one class; it now imports it
directly, and the test that patched `gooddata_pandas.dataframe.models.X` patches
`gooddata_pandas.dataframe.X`.
Its `[tool.ty.analysis]` allowed-unresolved-imports needed the `.**` glob that
gooddata-sdk already uses: the bare `gooddata_api_client` entry covered the old
aggregate import but not a leaf `gooddata_api_client.model.*` module.
perf: cut gooddata_sdk import cost by importing only what the SDK uses
yenkins-admin
requested review from
hkad98,
lupko and
pcerny
as code owners
September 16, 2026 13:56
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## rel/dev #1817 +/- ##
===========================================
+ Coverage 82.49% 82.55% +0.05%
===========================================
Files 283 324 +41
Lines 20448 20543 +95
===========================================
+ Hits 16869 16959 +90
- Misses 3579 3584 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🚀 Automated PR to perform merge from master into rel/dev with changes up to b1437c0 (created by https://github.com/gooddata/gooddata-python-sdk/actions/runs/35105169080).