summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorYoctopuce dev <dev@yoctopuce.com>2025-05-20 15:57:11 +0200
committerDamien George <damien@micropython.org>2025-06-23 16:05:12 +1000
commit66c01480226aaf880a9d6e88340772bd53bc0421 (patch)
treeb9b27e2c3705cc44c8548c230e4e808fc578863a /tests
parent35f15cfdf2a04a0ad290d70383fc3e4be4f79812 (diff)
py/runtime: Add support for using __all__ in star import.
When the symbol `__all__` is defined in a module, `mp_import_all()` should import all listed symbols into the global environment, rather than relying on the underscore-is-private default. This is the standard in CPython. Each item is loaded in the same way as if it would be an explicit import statement, and will invoke the module's `__getattr__` function if needed. This provides a straightforward solution for fixing star import of modules using a dynamic loader, such as `extmod/asyncio` (see issue #7266). This improvement has been enabled at BASIC_FEATURES level, to avoid impacting devices with limited ressources, for which star import is of little use anyway. Additionally, detailled reporting of errors during `__all__` import has been implemented to match CPython, but this is only enabled when ERROR_REPORTING is set to MICROPY_ERROR_REPORTING_DETAILED. Signed-off-by: Yoctopuce dev <dev@yoctopuce.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/cpydiff/core_import_all.py10
-rw-r--r--tests/cpydiff/modules3/__init__.py1
-rw-r--r--tests/cpydiff/modules3/foo.py2
-rw-r--r--tests/import/import_star.py59
-rw-r--r--tests/import/pkgstar_all_array/__init__.py49
-rw-r--r--tests/import/pkgstar_all_array/funcs.py2
-rw-r--r--tests/import/pkgstar_all_inval/__init__.py1
-rw-r--r--tests/import/pkgstar_all_miss/__init__.py8
-rw-r--r--tests/import/pkgstar_all_tuple/__init__.py22
-rw-r--r--tests/import/pkgstar_default/__init__.py20
10 files changed, 161 insertions, 13 deletions
diff --git a/tests/cpydiff/core_import_all.py b/tests/cpydiff/core_import_all.py
deleted file mode 100644
index 0fbe9d4d4..000000000
--- a/tests/cpydiff/core_import_all.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
-categories: Core,import
-description: __all__ is unsupported in __init__.py in MicroPython.
-cause: Not implemented.
-workaround: Manually import the sub-modules directly in __init__.py using ``from . import foo, bar``.
-"""
-
-from modules3 import *
-
-foo.hello()
diff --git a/tests/cpydiff/modules3/__init__.py b/tests/cpydiff/modules3/__init__.py
deleted file mode 100644
index 27a2bf2ad..000000000
--- a/tests/cpydiff/modules3/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ["foo"]
diff --git a/tests/cpydiff/modules3/foo.py b/tests/cpydiff/modules3/foo.py
deleted file mode 100644
index dd9b9d4dd..000000000
--- a/tests/cpydiff/modules3/foo.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def hello():
- print("hello")
diff --git a/tests/import/import_star.py b/tests/import/import_star.py
new file mode 100644
index 000000000..0947f6a83
--- /dev/null
+++ b/tests/import/import_star.py
@@ -0,0 +1,59 @@
+# test `from package import *` conventions, including __all__ support
+#
+# This test requires MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES
+
+try:
+ next(iter([]), 42)
+except TypeError:
+ # 2-argument version of next() not supported
+ # we are probably not at MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES
+ print('SKIP')
+ raise SystemExit
+
+# 1. test default visibility
+from pkgstar_default import *
+
+print('visibleFun' in globals())
+print('VisibleClass' in globals())
+print('_hiddenFun' in globals())
+print('_HiddenClass' in globals())
+print(visibleFun())
+
+# 2. test explicit visibility as defined by __all__ (as an array)
+from pkgstar_all_array import *
+
+print('publicFun' in globals())
+print('PublicClass' in globals())
+print('unlistedFun' in globals())
+print('UnlistedClass' in globals())
+print('_privateFun' in globals())
+print('_PrivateClass' in globals())
+print(publicFun())
+# test dynamic import as used in asyncio
+print('dynamicFun' in globals())
+print(dynamicFun())
+
+# 3. test explicit visibility as defined by __all__ (as an tuple)
+from pkgstar_all_tuple import *
+
+print('publicFun2' in globals())
+print('PublicClass2' in globals())
+print('unlistedFun2' in globals())
+print('UnlistedClass2' in globals())
+print(publicFun2())
+
+# 4. test reporting of missing entries in __all__
+try:
+ from pkgstar_all_miss import *
+
+ print("missed detection of incorrect __all__ definition")
+except AttributeError as er:
+ print("AttributeError triggered for bad __all__ definition")
+
+# 5. test reporting of invalid __all__ definition
+try:
+ from pkgstar_all_inval import *
+
+ print("missed detection of incorrect __all__ definition")
+except TypeError as er:
+ print("TypeError triggered for bad __all__ definition")
diff --git a/tests/import/pkgstar_all_array/__init__.py b/tests/import/pkgstar_all_array/__init__.py
new file mode 100644
index 000000000..4499a94d5
--- /dev/null
+++ b/tests/import/pkgstar_all_array/__init__.py
@@ -0,0 +1,49 @@
+__all__ = ['publicFun', 'PublicClass', 'dynamicFun']
+
+
+# Definitions below should always be imported by a star import
+def publicFun():
+ return 1
+
+
+class PublicClass:
+ def __init__(self):
+ self._val = 1
+
+
+# If __all__ support is enabled, definitions below
+# should not be imported by a star import
+def unlistedFun():
+ return 0
+
+
+class UnlistedClass:
+ def __init__(self):
+ self._val = 0
+
+
+# Definitions below should be not be imported by a star import
+# (they start with an underscore, and are not listed in __all__)
+def _privateFun():
+ return -1
+
+
+class _PrivateClass:
+ def __init__(self):
+ self._val = -1
+
+
+# Test lazy loaded function, as used by extmod/asyncio:
+# Works with a star import only if __all__ support is enabled
+_attrs = {
+ "dynamicFun": "funcs",
+}
+
+
+def __getattr__(attr):
+ mod = _attrs.get(attr, None)
+ if mod is None:
+ raise AttributeError(attr)
+ value = getattr(__import__(mod, globals(), locals(), True, 1), attr)
+ globals()[attr] = value
+ return value
diff --git a/tests/import/pkgstar_all_array/funcs.py b/tests/import/pkgstar_all_array/funcs.py
new file mode 100644
index 000000000..7540d70f6
--- /dev/null
+++ b/tests/import/pkgstar_all_array/funcs.py
@@ -0,0 +1,2 @@
+def dynamicFun():
+ return 777
diff --git a/tests/import/pkgstar_all_inval/__init__.py b/tests/import/pkgstar_all_inval/__init__.py
new file mode 100644
index 000000000..7022476c1
--- /dev/null
+++ b/tests/import/pkgstar_all_inval/__init__.py
@@ -0,0 +1 @@
+__all__ = 42
diff --git a/tests/import/pkgstar_all_miss/__init__.py b/tests/import/pkgstar_all_miss/__init__.py
new file mode 100644
index 000000000..d960c7d0e
--- /dev/null
+++ b/tests/import/pkgstar_all_miss/__init__.py
@@ -0,0 +1,8 @@
+__all__ = ('existingFun', 'missingFun')
+
+
+def existingFun():
+ return None
+
+
+# missingFun is not defined, should raise an error on import
diff --git a/tests/import/pkgstar_all_tuple/__init__.py b/tests/import/pkgstar_all_tuple/__init__.py
new file mode 100644
index 000000000..a97715ed3
--- /dev/null
+++ b/tests/import/pkgstar_all_tuple/__init__.py
@@ -0,0 +1,22 @@
+__all__ = ('publicFun2', 'PublicClass2')
+
+
+# Definitions below should always be imported by a star import
+def publicFun2():
+ return 2
+
+
+class PublicClass2:
+ def __init__(self):
+ self._val = 2
+
+
+# If __all__ support is enabled, definitions below
+# should not be imported by a star import
+def unlistedFun2():
+ return 0
+
+
+class UnlistedClass2:
+ def __init__(self):
+ self._val = 0
diff --git a/tests/import/pkgstar_default/__init__.py b/tests/import/pkgstar_default/__init__.py
new file mode 100644
index 000000000..4947e4ce7
--- /dev/null
+++ b/tests/import/pkgstar_default/__init__.py
@@ -0,0 +1,20 @@
+# When __all__ is undefined, star import should only
+# show objects that do not start with an underscore
+
+
+def visibleFun():
+ return 42
+
+
+class VisibleClass:
+ def __init__(self):
+ self._val = 42
+
+
+def _hiddenFun():
+ return -1
+
+
+class _HiddenClass:
+ def __init__(self):
+ self._val = -1