Repository navigation
Create a public API - #194
FrederikSchnack wants to merge 5 commits into
Conversation
Expose supported user-facing symbols from sympde.api without relying on package-level wildcard exports.
Remove package-level wildcard exports and obsolete SymPy compatibility helpers, migrate internal imports and tests to defining modules, and defer costly optional plotting and serialization imports.
Move maintained notebook material into tested documentation examples and remove obsolete tutorial helpers and notebook assets.
Describe flat public imports in the README and add the API reference and executable examples to the Sphinx navigation.
818b425 to
21102fb
Compare
yguclu
left a comment
There was a problem hiding this comment.
Great job!
My single big concern is using the new public interface sympde.api in the unit tests in the tests folders. I have three main reasons to believe we should not do it:
- The unit tests are not "public-facing", as they only exist to ensure that every library component works as intended.
- The unit tests should know exactly the SymPDE modules they import from, and show them to the developers.
- As things progress, we hope that our unit tests will approach 100% coverage of the library. This will make
sympde/api/__init__.pya huge file which is not easily maintained.
If we avoid using sympde.api in the unit tests, we could restrict its imports to those needed by the projects that we know for sure to depend on SymPDE. AFAIK those are just PSYDAC, IGA-Python, and GeLaTo. This will make the file much shorter, cleaner, and easier to maintain.
| ### Deprecated | ||
|
|
||
| - Package and subpackage imports no longer re-export public objects. Replace imports such as `from sympde.topology import Square` with `from sympde.api import Square`. | ||
|
|
There was a problem hiding this comment.
The usual meaning of "deprecation" is a feature that is not supposed to be used any longer, typically with a runtime warning. Further, a deprecated feature usually survives one or two release cycles before being removed, but we have no time for that here.
Since an error is raised immediately in our case, I recommend moving this point (also mentioning the PR number) to the "Removed" section
| - Convert the README to Markdown and expand the installation, testing, and documentation-build instructions. | ||
| - Use explicit internal imports and load optional dependencies only when needed. |
| - Add `sympde.api` as the supported flat public API. | ||
| - Add executable examples to the documentation. |
| @@ -1,7 +0,0 @@ | |||
| from .version import __version__ | |||
There was a problem hiding this comment.
Why should we remove this line? Importing __version__ directly from the top-level of a Python package is standard; for example, try from numpy import __version__ or from sympy import __version__
| from sphinx.ext.apidoc import main as sphinx_apidoc | ||
|
|
||
| from sympde import __version__ | ||
| from sympde.version import __version__ |
| # Define a custom mapping by specifying its coordinate expressions. | ||
| class ComplexSquareMapping(Mapping): | ||
| """Map ``(x1, x2)`` through the square of a complex number.""" | ||
|
|
||
| _expressions = { | ||
| 'x': 'A * (x1**2 - x2**2)', | ||
| 'y': '2 * A * x1 * x2', | ||
| } |
There was a problem hiding this comment.
The docstring sounds a bit obscure to me. Is this how the mapping is defined?
If so, I would write that explicitly
| def test_plot_unmapped_2d_domain(monkeypatch, tmp_path): | ||
| """Plotting an unmapped square must create a 2D identity mapping.""" | ||
| monkeypatch.setenv('MPLBACKEND', 'Agg') | ||
| monkeypatch.setenv('MPLCONFIGDIR', str(tmp_path)) | ||
| plot_domain(Square('Omega'), draw=False) |
There was a problem hiding this comment.
What is this unit test checking?
| # Imported lazily to avoid a module cycle: mapping depends | ||
| # on the differential-operator definitions in this module. | ||
| from .mapping import Jacobian, Mapping | ||
|
|
There was a problem hiding this comment.
Instead of writing "module cycle", I would use the more common words "circular dependency"
| # Imported lazily to avoid a module cycle: mapping depends | |
| # on the differential-operator definitions in this module. | |
| from .mapping import Jacobian, Mapping | |
| # Imported lazily to avoid a circular dependency: mapping | |
| # depends on the differential operators in this module. | |
| from .mapping import Jacobian, Mapping | |
| def integrate(expression): | ||
| """Integrate an expression over the problem domain.""" | ||
|
|
||
| return integral(domain, expression) |
There was a problem hiding this comment.
Do we really need this function? It is only used in this example, and only two times
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:
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.apinamespaceA new
sympde.apipackage provides a documented, flat namespace containing the supported user-facing objects.The API covers:
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:
__all__exists;__all__does not contain duplicates;2. Remove wildcard exports from package initializers
The previous package initializers recursively imported and re-exported entire subpackages:
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:
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:
Internal library code imports directly from the module that defines each object:
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:
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:
5. Import plotting dependencies only when plotting is requested
matplotlib.pyplotwas previously imported whensympde.utilities.utilswas loaded, even when no plotting functionality was used.It is now imported locally by the plotting functions:
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:
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:
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.
MappingandJacobianare 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:
10. Migrate tests to the supported API
User-facing tests now import objects from
sympde.apirather 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:
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:
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:
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:
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:
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:
to:
Code that intentionally depends on internal implementation details may instead import directly from the defining module:
The
sympde.apinamespace is the recommended interface for downstream users.Validation
The complete test suite passes:
The documentation validation also passes: