Skip to content

Create a public API - #194

Open
FrederikSchnack wants to merge 3 commits into
upgrade_sympyfrom
public_api
Open

FrederikSchnack wants to merge 3 commits into
upgrade_sympyfrom
public_api

Conversation

@FrederikSchnack

@FrederikSchnack FrederikSchnack commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This is the final PR of this stack.

Introduce a single, explicit public API for SymPDE and reduce the amount of code and third-party functionality loaded during package imports.

User-facing code should now import supported objects from:

from sympde.api import Domain, Square, Mapping
from sympde.api import ScalarFunctionSpace, elements_of
from sympde.api import BilinearForm, LinearForm, integral

Internal SymPDE modules continue to import objects directly from their defining modules.

Closes #180
Closes #181
Closes #182

Public API changes

1. Add an explicit sympde.api namespace

A new sympde.api package provides a documented, flat namespace containing the supported user-facing objects.

The API covers:

  • symbolic constants and calculus operators;
  • domains, boundaries, interfaces, and mappings;
  • scalar and vector function spaces;
  • symbolic functions and differential operators;
  • variational forms, equations, norms, and integrals;
  • exterior-calculus objects;
  • printers and utility functions;
  • the package version.

The namespace defines an explicit __all__, making it possible to identify the supported API without relying on implementation details or wildcard imports.

Tests verify that:

  • every name in __all__ exists;
  • __all__ does not contain duplicates;
  • no unintended public names leak into the namespace;
  • exported objects are the original objects from their defining modules, rather than wrappers or duplicate definitions.

2. Remove wildcard exports from package initializers

The previous package initializers recursively imported and re-exported entire subpackages:

from .core import *
from .topology import *
from .exterior import *

Importing a small part of SymPDE could therefore load most of the library, including modules unrelated to the requested functionality.

The initializers for the following packages are now empty:

sympde
sympde.calculus
sympde.core
sympde.expr
sympde.exterior
sympde.printing
sympde.topology

This makes imports more predictable and prevents package initialization from eagerly traversing the full dependency graph.

3. Establish separate user-facing and internal import conventions

User code should use the convenient public namespace:

from sympde.api import Domain, Square, grad

Internal library code imports directly from the module that defines each object:

from sympde.topology.domain import Domain, Square
from sympde.calculus.core import grad

This separation provides a stable user-facing API while keeping internal dependencies explicit and avoiding circular imports.

Import-performance improvements

4. Avoid loading the complete library for package imports

Previously, imports such as:

import sympde
import sympde.topology.domain

executed wildcard imports from multiple package initializers. This loaded many unrelated symbolic, plotting, mapping, and expression modules.

With empty package initializers, importing a module now loads only its actual dependencies. The complete user-facing namespace is loaded only when explicitly requested through:

import sympde.api

5. Import plotting dependencies only when plotting is requested

matplotlib.pyplot was previously imported when sympde.utilities.utils was loaded, even when no plotting functionality was used.

It is now imported locally by the plotting functions:

plot_2d(...)
plot_3d(...)

This avoids Matplotlib initialization, backend selection, and configuration-directory access during unrelated SymPDE imports.

6. Import serialization dependencies only when needed

The domain module previously imported h5py, yaml, and additional NumPy functionality at module import time.

These dependencies are now imported inside the operations that require them:

domain.export(...)
Domain.from_file(...)

Creating or manipulating symbolic domains therefore no longer initializes the HDF5 and YAML stacks.

7. Remove unnecessary production imports

A large number of unused imports were removed from the core, topology, exterior-calculus, expression, and printing modules.

This includes unused:

  • NumPy imports;
  • SymPy symbols and matrix classes;
  • traversal and simplification helpers;
  • mapping and topology classes;
  • calculus operators;
  • legacy compatibility helpers.

Besides reducing import work, this makes each module’s dependency requirements easier to understand.

8. Break circular imports with local imports

The topology derivative module previously imported mapping classes at module scope, while the mapping module also depended on derivative definitions.

Mapping and Jacobian are now imported locally only in the logical-derivative path that needs them.

This removes the module-level cycle without changing the public behavior of differential operators.

Compatibility and cleanup

9. Remove the obsolete SymPy compatibility module

The previous PR replaced usages of the local compatibility helpers with maintained SymPy utilities and native Python metaclass syntax.

Once the package-level wildcard exports are removed, the remaining compatibility module is no longer part of the import surface and can be deleted.

This removes:

sympde/old_sympy_utilities.py

10. Migrate tests to the supported API

User-facing tests now import objects from sympde.api rather than relying on wildcard package exports.

This ensures that the test suite exercises the same import path recommended to downstream users.

Dedicated API tests verify:

  • package initializers remain empty;
  • the API contains only explicitly exported names;
  • API exports refer to the canonical definitions;
  • no duplicate names are present.

The API checks use direct imports and assertions rather than dynamically executing generated import statements.

11. Fix plotting of unmapped two-dimensional domains

While adding coverage for deferred plotting imports, the unmapped 2D plotting path was found to create a three-dimensional identity mapping.

It now creates the correct two-dimensional identity mapping:

IdentityMapping('Id', dim=2)

A regression test plots an unmapped square with a non-interactive Matplotlib backend.

Documentation changes

12. Document the supported public API

The README and Sphinx documentation now direct users to sympde.api.

A dedicated API page documents the complete explicit namespace:

from sympde.api import Cube, Mapping, Constant
from sympde.api import ScalarFunctionSpace, elements_of
from sympde.api import BilinearForm, LinearForm, Norm

The API documentation avoids generating duplicate cross-reference targets for objects that are also documented in their defining modules.

13. Replace obsolete notebooks with executable examples

The old notebooks and their Binder integration had become outdated and were not validated by CI.

They are replaced by focused Python examples covering:

  • a quick start;
  • analytical mappings;
  • differential calculus;
  • exterior calculus;
  • nonlinear Poisson equations;
  • tensorization;
  • variational forms and multipatch interfaces.

All examples use the new public API.

14. Execute documentation examples in CI

The documentation workflow now runs every Python example before building the Sphinx pages:

for example in doc/examples/*.py; do
    python "$example"
done

This ensures that documented imports and examples remain executable as the API evolves.

Obsolete documentation helper scripts, notebooks, notebook styles, and the Binder badge are removed.

Breaking changes

Wildcard convenience imports from package namespaces are no longer supported.

For example, user code should migrate from:

from sympde.topology import Domain, Square
from sympde.expr import BilinearForm
from sympde.calculus import grad

to:

from sympde.api import Domain, Square, BilinearForm, grad

Code that intentionally depends on internal implementation details may instead import directly from the defining module:

from sympde.topology.domain import Domain, Square
from sympde.expr.expr import BilinearForm
from sympde.calculus.core import grad

The sympde.api namespace is the recommended interface for downstream users.

Validation

The complete test suite passes:

174 passed, 2 skipped

The documentation validation also passes:

  • every documentation example executes successfully;
  • Sphinx builds successfully with warnings treated as errors;
  • the explicit API namespace is checked for missing, duplicate, or unintended exports;
  • public API objects are verified against their canonical definitions;
  • deferred plotting imports are covered with a non-interactive regression test.

Expose supported symbols through sympde.api, migrate tests and API-facing documentation, remove package-level wildcard exports and obsolete compatibility code, and lazily load optional plotting and serialization dependencies.
@FrederikSchnack
FrederikSchnack added this pull request to stack #192 September 29, 2026 15:37
@FrederikSchnack FrederikSchnack mentioned this pull request Sep 29, 2026
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.

Clean up and speed up library import Remove unnecessary imports from __init__.py files Avoid useless imports

1 participant