Skip to content

[bot] Merge master/b1437c08 into rel/dev - #1817

Merged
yenkins-admin merged 2 commits into
rel/devfrom
snapshot-master-b1437c08-to-rel/dev
Sep 16, 2026
Merged

yenkins-admin merged 2 commits into
rel/devfrom
snapshot-master-b1437c08-to-rel/dev

Conversation

@yenkins-admin

Copy link
Copy Markdown
Contributor

🚀 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).

hkad98 and others added 2 commits September 16, 2026 15:49
`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
yenkins-admin merged commit 9b4f653 into rel/dev Sep 16, 2026
2 checks passed
@yenkins-admin
yenkins-admin deleted the snapshot-master-b1437c08-to-rel/dev branch September 16, 2026 13:56
@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.38220% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.55%. Comparing base (b145e1a) to head (b1437c0).
⚠️ Report is 586 commits behind head on rel/dev.

Files with missing lines Patch % Lines
packages/gooddata-sdk/src/gooddata_sdk/__init__.py 88.23% 2 Missing ⚠️
...ta_source/entity_model/content_objects/__init__.py 0.00% 2 Missing ⚠️
packages/gooddata-sdk/src/gooddata_sdk/_lazy.py 91.66% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants