New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Remove star imports, pin down public namespace #3764
Conversation
pyvista/__init__.py
Outdated
from pyvista.utilities import ( | ||
# arrays, | ||
# cells, | ||
# common, | ||
# errors, | ||
# features, | ||
fileio, | ||
# geometric_objects, | ||
# helpers, | ||
# misc, | ||
# parametric_objects, | ||
reader, | ||
# regression, | ||
# sphinx_gallery, | ||
transformations, | ||
# wrappers, | ||
# xvfb, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only kept subpackages here that broke our own CI. Note that if we delete things from here it should only affect
import pyvista
pyvista.misc.something
whereas pyvista.utilities.misc.something
could still work, especially if one does from pyvista.utilities.misc import something
.
from .misc import ( | ||
PyVistaDeprecationWarning, | ||
_get_vtk_id_type, | ||
_set_plot_theme_from_env, | ||
set_pickle_format, | ||
vtk_version_info, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added this block because it was missing. This makes the API surface here larger, but it seemed more consistent with the status quo, and we can still remove what's unnecessary here later in the life of this PR. This also shows how names can be redundant: since we're using from pyvista.utilities.misc import ...
in pyvista.__init__
, we don't really have to have these here.
Depending on how we decide to structure the higher-level inits, we might end up with very lean subpackage inits.
pyvista/utilities/__init__.py
Outdated
# submodules: | ||
# 'arrays', | ||
# 'cells', | ||
# 'common', | ||
# 'errors', | ||
# 'features', | ||
'fileio', | ||
# 'geometric_objects', | ||
# 'helpers', | ||
# 'misc', | ||
# 'parametric_objects', | ||
'reader', | ||
# 'regression', | ||
# 'sphinx_gallery', | ||
'transformations', | ||
# 'wrappers', | ||
# 'xvfb', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Subpackages commented out to match imports in the main pyvista.__init__
...
if dunder_all is None: | ||
# nothing to go wrong | ||
pytest.skip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured we should skip subpackages which don't have an __all__
(which is most of our subpackages). If we go forward with this PR then we'll want to end up with __all__
s anywhere, so it's a bit of an incentive to see SKIPPED instead of PASSED for these subpackages.
Codecov Report
@@ Coverage Diff @@
## main #3764 +/- ##
=======================================
Coverage 94.30% 94.30%
=======================================
Files 94 94
Lines 20321 20331 +10
=======================================
+ Hits 19163 19173 +10
Misses 1158 1158 |
I haven't thought about all this stuff, but just my general thought to guide how I try to think about things like this: I typically try to follow whatever the greater scientific Python stack does. For example I often look toward NumPy, since they have to deal with a ton of downstream users, a lot of code to maintain, finite bandwidth, and they think pretty hard about this stuff (and as an aside, I tend to appreciate / agree with their choices!). And by following their example, it's likely that things will work properly, and expertise can be shared, etc. In this particular case, it looks like they do set https://github.com/numpy/numpy/blob/68d7aadd30cb7a4f6f32e76715b38b644df18602/numpy/__init__.py#L194 It seems like a high one-time cost to add this stuff, but after that it's not too bad. In SciPy we have
... so if we follow NumPy, they don't appear to adhere to this, because of the |
Thanks for the points @larsoner. Although some of the plumbing under numpy is terrifying, these are great data points :) |
The doc build breakage is very confusing. It has one error for each of the includes in
In the .rst we refer to pyvista/doc/api/core/helpers.rst Line 10 in 52b81ae
The catch is that the alias was already present, even though in What's confusing is that the error sounds like it's trying to look up the names in |
Nope, I just botched my local check. In the breaking commit I had $ python -c 'import pyvista; print(pyvista.helpers)'
<module 'pyvista.plotting.helpers' from '/home/adeak/pyvista/pyvista/plotting/helpers.py'> which explains everything. Restoring the right Goes to show how we're on the right path by removing star imports :D |
pyvista/__init__.py
Outdated
from pyvista.core import * | ||
from pyvista.utilities import ( | ||
# arrays, | ||
cells, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added back pyvista.cells
because examples/00-load/linear-cells.py
references pyvista.cells.Wedge
.
This must have caused broken intersphinx references in previous versions of this PR, so I'll have to test locally if these emit warnings, or can be told to do so. I wouldn't want to end up with a bunch of silently broken doc links due to this PR.
pyvista/__init__.py
Outdated
# common, | ||
# errors, | ||
# features, | ||
fileio, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is here because we have a pyvista.fileio.get_ext(...)
in pyvista/plotting/plotting.py
, but we also have pyvista.get_ext()
so we should probably remove either.
pyvista/__init__.py
Outdated
# features, | ||
fileio, | ||
# geometric_objects, | ||
helpers, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is here due to doc/api/core/helpers.rst
so it's probably here for good, pyvista.helpers
is likely to be used downstream.
pyvista/__init__.py
Outdated
helpers, | ||
# misc, | ||
# parametric_objects, | ||
reader, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is here for a pyvista.reader.MultiBlockPlot3DReader
and a pyvista.reader.Plot3DFunctionEnum
, both available at the top-level namespace. But perhaps downstream expects this too...
pyvista/__init__.py
Outdated
reader, | ||
# regression, | ||
# sphinx_gallery, | ||
transformations, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is here for pyvista.transformations.apply_transformation_to_points
(in docs) and pyvista.transformations.axis_angle_rotation
(in a handful of tests), and neither names are available at the top-level namespace.
@@ -356,7 +356,7 @@ def test_triangulate_inplace(hexbeam): | |||
|
|||
|
|||
@pytest.mark.parametrize('binary', [True, False]) | |||
@pytest.mark.parametrize('extension', pyvista.pointset.UnstructuredGrid._WRITERS) | |||
@pytest.mark.parametrize('extension', pyvista.UnstructuredGrid._WRITERS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this and the other test in this module because these were the only internal uses of pyvista.pointset
, and considering its contents it's very likely that anyone would/should be using the top-level names, as it was changed here.
# widgets, | ||
) | ||
|
||
from pyvista.utilities import ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've merged the imports from pyvista.utilities.*
into one big import. It seems more maintainable to let subpackages be responsible for their internal organization. If we spell out from pyvista.utilities.misc import ...
here, future refactors moving things around in subpackages would make us have to change multiple imports. With the new logistics here we let the subdivision of pyvista.utilities.*
be the private business of pyvista/utilities/__init__.py
.
tests/test_theme.py
Outdated
from pyvista import colors | ||
from pyvista.plotting import colors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've since restored the pyvista.colors
name, but it might be cleaner to keep the "proper" import in the test here, not sure.
# pointset, | ||
) | ||
|
||
# mypy gets confused if this is added to the pyvista.core import: | ||
from pyvista.core.pyvista_ndarray import pyvista_ndarray |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a major thorn in my side, but if I add pyvista_ndarray
to the preceding block of imports (where it belongs) mypy
thinks that what gets imported is the pyvista.core.pyvista_ndarray
module rather than the pyvista.core.pyvista_ndarray.pyvista_ndarray
class. Despite the fact that in pyvista/core/__init__.py
it correctly finds that from .pyvista_ndarray import pyvista_ndarray
refers to the class, not the module. So this seems like a mypy
issue. I have no other idea to work around this than to use this ugly fully qualified import in pyvista/__init__.py
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I doubt anyone in the wild has done from pyvista.core.pyvista_ndarray import pyvista_ndarray
(right?) in which case you could git mv pyvista/core/pyvista_ndarray.py pyvista/core/_pyvista_ndarray.py
and then adjust the imports. Seems cleaner anyway since pyvista.core.pyvista_ndarray
is not meant to be a public module I assume...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we're currently offering a very broad API surface (mostly unintentionally I think). These are partly the kinds of "public API paring down" steps I have in mind, but need to discuss in detail to make sure we're all on board and don't break downstream (or at least are ready to do that break and urge downstream to use proper import paths).
I'd like for us to put full attention on this and help finish this up for a |
* upstream/main: (99 commits) [HOTFIX] Resolve trame import issues (pyvista#3943) Bump trimesh from 3.18.2 to 3.18.3 (pyvista#3940) Release 0.38 (pyvista#3935) Document BasePlotter (pyvista#3934) UnstructuredGrid volume rendering (pyvista#3930) Force redeploy of docs (pyvista#3933) Add intersection edge case test (pyvista#3929) Fix and improve chart interaction (pyvista#3756) [create-pull-request] update local intersphinx (pyvista#3926) Track is_set on Camera (pyvista#3927) Custom themes and more theme options (pyvista#3870) ignore BLK100 for flake8-black (pyvista#3928) Verify int64 support with trame (pyvista#3924) Bump trimesh from 3.18.1 to 3.18.2 (pyvista#3923) Add hydrogen orbitals example (pyvista#3825) Ignore inherited members and attributes and switch to the full doc build (pyvista#3919) Support axes customization (pyvista#3656) Add BaseVTKReader for custom Reader; Use for PVDReader (pyvista#3894) Fix a small typo (pyvista#3917) Use plotter window_size for trame viewer if set (pyvista#3909) ...
Branch partly updated. Will have to manually add all the names that were added to
and after some cleanup of the output I have
Almost all these names will have to be imported and added to the respective
@banesullivan are you sure it's a good idea to add potentially breaking namespace changes to a patch release? |
ah. If things are breaking, no. I need to dive into this PR and see how much this is breaking and see what we can preserve |
There's two parts of this PR:
The work already done is for point 1 and almost all done (pending work in previous comment). The discussion I was hoping for here is for point 2. But you said
Any such change can come from point 2 (if I do point 1 right). Solving "namespace issues" is inherently a breaking change because it will involve changing names in the API. |
I've added everything from the above list with the exception of
The first two names seemed missing from the public API entirely, and while the last one is visible via TODO still:
|
Overview
In a third-party PR thread it was pointed out that
mypy
makes some odd, opinionated choices when it handles the--strict
flag. In a nutshell: whenpyvista
is installed as a dependency downstream and someone runsmypy --strict foo.py
on a script that usespyvista
,mypy
pretends that it doesn't know thatpyvista
has any attributes. For a bit more context see thismypy
issue and this flag in themypy
docs.So
mypy
would refuse to find attributes anyway,from subpackage import name as name
(over my dead body) or dofrom subpackage import name, other_name, ...
but additionally define__all__
(a list normally used to fine-tune star import behaviour; something our users shouldn't be doing anyway).I don't like the necessary workaround with
__all__
one bit, but getting rid of star imports is a reasonably noble task, so we might as well add some dirt to makemypy
happy, since this would evidently benefit downstream libraries and maybe even some end users.Details
What this is
This PR (expected to be WIP for a while) is meant as a step to remove the star imports, in order to facilitate discussions concerning concrete implementations. Since there are quite a few heavy design decisions to be made, I figured it's probably easier to talk about this if we have specifics to look at.
So as of the last commit when this PR was opened (5ade1f0) this is a first-guess attempt at removing star imports from the
pyvista.utilities
subpackage tree. I plan to progress with other subpackages in time in this PR, also as a function of any discussion here. For now the implementation intends to keep the API surface the same (especially for the mainpyvista
namespace), but I would recommend taking the opportunity to scrap whatever we can from the public API to get rid of some bloat.Remarks
In no particular order of importance:
pyvista
namespace inpyvista.__all__
, with the exception of names coming from the standard library or third-party modules (e.g.pyvista.np
,pyvista.pi
). I have, however, commented out a bunch of private-looking or unnecessary-looking things from the list, with the plan to delete these (and hopefully a bunch of others) before this PR is done.__all__
s contained in__init__.py
s only contain names that actually exist in the given namespace. I think this is useful, but I don't insist on this having its own test module.pyvista.__all__
andpyvista.utilities.__all__
. I think we should ideally only end up with any name being in at most one of these lists: ifpyvista.utilities.misc.PyVistaDeprecationWarning
is exposed aspyvista.PyVistaDeprecationWarning
then we should not add this item topyvista.utilities.__all__
. (See also some of the questions below.)(See also remarks below that I'll post as comments in the diff.)
Open questions
__all__
s to makemypy
happy?__all__
s, e.g.pyvista.utilities.misc.__all__
? I think not, because we're not actually trying to facilitate star imports, andmypy
can surely figure out what names are available in actual "worker" modules.__all__
? This could hint to users to only access a given attribute at the highest level where it's exposed. And this could help reduce the public API surface by explicitly pushing back less-important public attributes to lower-level__all__
s.__init__.py
s (specifically,pyvista.__init__
), do we want to do things likefrom pyvista.utilities.misc import ...
(as it is in this PR when it's opened), or do we want to build up subpackage namespaces level by level, and dofrom pyvista.utilities import ...
(with one large wall of imports per top-level subpackage)? (If we snowball namespaces like this then this is probably a contradictory choice to the previous question. This snowballing would be mostly what we're doing now.)from ... import
statement)? Do whatisort
would do? Group semantically? Group alphabetically, uppercase before/intermixed with lowercase names?pyvista.utilities.*
? Do you think end users might rely on these? I took a glance atpyvistaqt
(1.5th party library) andmne-python
(3rd party direct dependent), and they don't seem to be doing any weird things, and any subpackages they access are inpyvista.plotting
(which we'll probably want to mostly preserve anyway...).Example
Here's a simple script demonstrating the typing issues:
When I run this function in an env where
pyvista
is a regular installed package, it runs fine, meaning that all of the above attributes exist in the respective namespaces. Runningmypy --strict
on the above script onmain
doesand on this PR's branch (as of opening this PR) it does
The reason for this remaining error is that even though we have
'PolyData'
inpyvista.__all__
, the remaining star import inpyvista.__init__
that pulls in the name frompyvista.core
implies thatmypy
is blind to it. The other errors from earlier were resolved because in each__init__.py
there are explicit imports and explicit mentions of'perlin_noise'
in__all__
.@pyvista/developers please chime in if you have the time and the opinions to decide how to tackle the fine details (and the broad ones) here. Even (especially) if you think we should take a very different road from what I've shown here. And please raise any points I missed.