Skip to content

Commit cf5f760

Browse files
authored
Merge pull request github#5582 from RasmusWL/all-tuple
Python: Add support for `__all__` assigned to tuple
2 parents 51bab81 + bc49bc7 commit cf5f760

File tree

10 files changed

+145
-1
lines changed

10 files changed

+145
-1
lines changed

python/ql/src/semmle/python/Module.qll

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ class Module extends Module_, Scope, AstNode {
129129
a.defines(all) and
130130
a.getScope() = this and
131131
all.getId() = "__all__" and
132-
a.getValue().(List).getAnElt().(StrConst).getText() = name
132+
(
133+
a.getValue().(List).getAnElt().(StrConst).getText() = name
134+
or
135+
a.getValue().(Tuple).getAnElt().(StrConst).getText() = name
136+
)
133137
)
134138
}
135139

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| all_dynamic.py:0:0:0:0 | Module all_dynamic | foo |
2+
| all_list.py:0:0:0:0 | Module all_list | bar |
3+
| all_list.py:0:0:0:0 | Module all_list | foo |
4+
| all_tuple.py:0:0:0:0 | Module all_tuple | bar |
5+
| all_tuple.py:0:0:0:0 | Module all_tuple | foo |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import python
2+
3+
from Module mod, string name
4+
where mod.declaredInAll(name)
5+
select mod, name
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
foo = "foo"
2+
bar = "bar"
3+
baz = "baz"
4+
5+
6+
__all__ = ["foo"]
7+
8+
__all__ += ["bar"]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# I (@RasmusWL) have not seen anyone use this pattern in real code,
2+
# so this example is only included for completeness.
3+
foo = "foo"
4+
bar = "bar"
5+
baz = "baz"
6+
7+
8+
temp = ["foo", "bar"]
9+
__all__ = temp
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
foo = "foo"
2+
bar = "bar"
3+
baz = "baz"
4+
5+
6+
__all__ = ["foo", "bar"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
foo = "foo"
2+
bar = "bar"
3+
baz = "baz"
4+
5+
6+
__all__ = {"foo", "bar"}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
foo = "foo"
2+
bar = "bar"
3+
baz = "baz"
4+
5+
6+
__all__ = ("foo", "bar")
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# This file showcases how imports work with `__all__`.
2+
#
3+
# TL;DR; in `from <module> import *`, if `__all__` is defined in `<module>`, then only
4+
# the names in `__all__` will be imported -- otherwise all names that doesn't begin with
5+
# an underscore will be imported.
6+
#
7+
# If `__all__` is defined, `__all__` must be a sequence for `from <module> import *` to work.
8+
#
9+
# https://docs.python.org/3/reference/simple_stmts.html#the-import-statement
10+
# https://docs.python.org/3/glossary.html#term-sequence
11+
12+
print("import *")
13+
print("---")
14+
15+
from no_all import *
16+
print("no_all.py")
17+
print(" foo={!r}".format(foo))
18+
print(" bar={!r}".format(bar))
19+
print(" baz={!r}".format(baz))
20+
try:
21+
print(" _qux={!r}".format(_qux))
22+
except NameError:
23+
print(" _qux not imported")
24+
del foo, bar, baz
25+
26+
from all_list import *
27+
print("all_list.py")
28+
print(" foo={!r}".format(foo))
29+
print(" bar={!r}".format(bar))
30+
try:
31+
print(" baz={!r}".format(baz))
32+
except NameError:
33+
print(" baz not imported")
34+
del foo, bar
35+
36+
from all_tuple import *
37+
print("all_tuple.py")
38+
print(" foo={!r}".format(foo))
39+
print(" bar={!r}".format(bar))
40+
try:
41+
print(" baz={!r}".format(baz))
42+
except NameError:
43+
print(" baz not imported")
44+
del foo, bar
45+
46+
from all_dynamic import *
47+
print("all_dynamic.py")
48+
print(" foo={!r}".format(foo))
49+
print(" bar={!r}".format(bar))
50+
try:
51+
print(" baz={!r}".format(baz))
52+
except NameError:
53+
print(" baz not imported")
54+
del foo, bar
55+
56+
from all_indirect import *
57+
print("all_indirect.py")
58+
print(" foo={!r}".format(foo))
59+
print(" bar={!r}".format(bar))
60+
try:
61+
print(" baz={!r}".format(baz))
62+
except NameError:
63+
print(" baz not imported")
64+
del foo, bar
65+
66+
# Example of wrong definition of `__all__`, where it is not a sequence.
67+
try:
68+
from all_set import *
69+
except TypeError as e:
70+
assert str(e) == "'set' object does not support indexing"
71+
print("from all_set import * could not be imported:", e)
72+
73+
print("")
74+
print("Direct reference on module")
75+
print("---")
76+
# Direct reference always works, no matter how `__all__` is set.
77+
import no_all
78+
import all_list
79+
import all_tuple
80+
import all_dynamic
81+
import all_indirect
82+
import all_set
83+
84+
for mod in [no_all, all_list, all_tuple, all_dynamic, all_indirect, all_set]:
85+
print("{}.py".format(mod.__name__))
86+
print(" foo={!r}".format(mod.foo))
87+
print(" bar={!r}".format(mod.bar))
88+
print(" baz={!r}".format(mod.baz))
89+
90+
print("\nspecial: no_all._qux={!r}".format(no_all._qux))
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
foo = "foo"
2+
bar = "bar"
3+
baz = "baz"
4+
# When `__all__` is not defined, names starting with underscore is not imported with `from <module> import *`
5+
_qux = "qux"

0 commit comments

Comments
 (0)