Skip to content

Commit 2ca2eb4

Browse files
chadrikilevkivskyi
authored andcommitted
Add --include-private option to stubgen to include private members. (#3367)
* Add --include-private option to stubgen to include private members. * use "flags" instead of "opts" to identify cli flags in stub tests. * require the list of arguments to be passed to stubgen.parse_options.
1 parent 3589c08 commit 2ca2eb4

File tree

3 files changed

+111
-17
lines changed

3 files changed

+111
-17
lines changed

mypy/stubgen.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
('modules', List[str]),
7474
('ignore_errors', bool),
7575
('recursive', bool),
76+
('include_private', bool),
7677
])
7778

7879

@@ -86,7 +87,8 @@ def generate_stub_for_module(module: str, output_dir: str, quiet: bool = False,
8687
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
8788
no_import: bool = False,
8889
search_path: List[str] = [],
89-
interpreter: str = sys.executable) -> None:
90+
interpreter: str = sys.executable,
91+
include_private: bool = False) -> None:
9092
target = module.replace('.', '/')
9193
try:
9294
result = find_module_path_and_all(module=module,
@@ -118,7 +120,7 @@ def generate_stub_for_module(module: str, output_dir: str, quiet: bool = False,
118120
target = os.path.join(output_dir, target)
119121
generate_stub(module_path, output_dir, module_all,
120122
target=target, add_header=add_header, module=module,
121-
pyversion=pyversion)
123+
pyversion=pyversion, include_private=include_private)
122124
if not quiet:
123125
print('Created %s' % target)
124126

@@ -185,7 +187,8 @@ def load_python_module_info(module: str, interpreter: str) -> Tuple[str, Optiona
185187

186188
def generate_stub(path: str, output_dir: str, _all_: Optional[List[str]] = None,
187189
target: str = None, add_header: bool = False, module: str = None,
188-
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION
190+
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
191+
include_private: bool = False
189192
) -> None:
190193
with open(path, 'rb') as f:
191194
source = f.read()
@@ -199,7 +202,7 @@ def generate_stub(path: str, output_dir: str, _all_: Optional[List[str]] = None,
199202
sys.stderr.write('%s\n' % m)
200203
sys.exit(1)
201204

202-
gen = StubGenerator(_all_, pyversion=pyversion)
205+
gen = StubGenerator(_all_, pyversion=pyversion, include_private=include_private)
203206
ast.accept(gen)
204207
if not target:
205208
target = os.path.join(output_dir, os.path.basename(path))
@@ -223,7 +226,8 @@ def generate_stub(path: str, output_dir: str, _all_: Optional[List[str]] = None,
223226

224227

225228
class StubGenerator(mypy.traverser.TraverserVisitor):
226-
def __init__(self, _all_: Optional[List[str]], pyversion: Tuple[int, int]) -> None:
229+
def __init__(self, _all_: Optional[List[str]], pyversion: Tuple[int, int],
230+
include_private: bool = False) -> None:
227231
self._all_ = _all_
228232
self._output = [] # type: List[str]
229233
self._import_lines = [] # type: List[str]
@@ -235,6 +239,7 @@ def __init__(self, _all_: Optional[List[str]], pyversion: Tuple[int, int]) -> No
235239
self._classes = set() # type: Set[str]
236240
self._base_classes = [] # type: List[str]
237241
self._pyversion = pyversion
242+
self._include_private = include_private
238243

239244
def visit_mypy_file(self, o: MypyFile) -> None:
240245
self._classes = find_classes(o)
@@ -515,6 +520,8 @@ def is_not_in_all(self, name: str) -> bool:
515520
return False
516521

517522
def is_private_name(self, name: str) -> bool:
523+
if self._include_private:
524+
return False
518525
return name.startswith('_') and (not name.endswith('__')
519526
or name in ('__all__',
520527
'__author__',
@@ -610,7 +617,7 @@ def walk_packages(packages: List[str]) -> Iterator[str]:
610617

611618

612619
def main() -> None:
613-
options = parse_options()
620+
options = parse_options(sys.argv[1:])
614621
if not os.path.isdir('out'):
615622
raise SystemExit('Directory "out" does not exist')
616623
if options.recursive and options.no_import:
@@ -636,23 +643,24 @@ def main() -> None:
636643
pyversion=options.pyversion,
637644
no_import=options.no_import,
638645
search_path=options.search_path,
639-
interpreter=options.interpreter)
646+
interpreter=options.interpreter,
647+
include_private=options.include_private)
640648
except Exception as e:
641649
if not options.ignore_errors:
642650
raise e
643651
else:
644652
print("Stub generation failed for", module, file=sys.stderr)
645653

646654

647-
def parse_options() -> Options:
648-
args = sys.argv[1:]
655+
def parse_options(args: List[str]) -> Options:
649656
pyversion = defaults.PYTHON3_VERSION
650657
no_import = False
651658
recursive = False
652659
ignore_errors = False
653660
doc_dir = ''
654661
search_path = [] # type: List[str]
655662
interpreter = ''
663+
include_private = False
656664
while args and args[0].startswith('-'):
657665
if args[0] == '--doc-dir':
658666
doc_dir = args[1]
@@ -673,6 +681,8 @@ def parse_options() -> Options:
673681
pyversion = defaults.PYTHON2_VERSION
674682
elif args[0] == '--no-import':
675683
no_import = True
684+
elif args[0] == '--include-private':
685+
include_private = True
676686
elif args[0] in ('-h', '--help'):
677687
usage()
678688
else:
@@ -689,7 +699,8 @@ def parse_options() -> Options:
689699
interpreter=interpreter,
690700
modules=args,
691701
ignore_errors=ignore_errors,
692-
recursive=recursive)
702+
recursive=recursive,
703+
include_private=include_private)
693704

694705

695706
def default_python2_interpreter() -> str:
@@ -721,6 +732,9 @@ def usage() -> None:
721732
--no-import don't import the modules, just parse and analyze them
722733
(doesn't work with C extension modules and doesn't
723734
respect __all__)
735+
--include-private
736+
generate stubs for objects and members considered private
737+
(single leading undescore and no trailing underscores)
724738
--doc-dir PATH use .rst documentation in PATH (this may result in
725739
better stubs in some cases; consider setting this to
726740
DIR/Python-X.Y.Z/Doc/library)

mypy/test/teststubgen.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import tempfile
88
import time
9+
import re
910
from types import ModuleType
1011

1112
from typing import List, Tuple
@@ -16,7 +17,7 @@
1617
from mypy.test import config
1718
from mypy.parse import parse
1819
from mypy.errors import CompileError
19-
from mypy.stubgen import generate_stub, generate_stub_for_module
20+
from mypy.stubgen import generate_stub, generate_stub_for_module, parse_options, Options
2021
from mypy.stubgenc import generate_c_type_stub, infer_method_sig
2122
from mypy.stubutil import (
2223
parse_signature, parse_all_signatures, build_signature, find_unique_signatures,
@@ -104,11 +105,21 @@ def cases(self) -> List[DataDrivenTestCase]:
104105
return c
105106

106107

108+
def parse_flags(program_text: str) -> Options:
109+
flags = re.search('# flags: (.*)$', program_text, flags=re.MULTILINE)
110+
if flags:
111+
flag_list = flags.group(1).split()
112+
else:
113+
flag_list = []
114+
return parse_options(flag_list + ['dummy.py'])
115+
116+
107117
def test_stubgen(testcase: DataDrivenTestCase) -> None:
108118
if 'stubgen-test-path' not in sys.path:
109119
sys.path.insert(0, 'stubgen-test-path')
110120
os.mkdir('stubgen-test-path')
111121
source = '\n'.join(testcase.input)
122+
options = parse_flags(source)
112123
handle = tempfile.NamedTemporaryFile(prefix='prog_', suffix='.py', dir='stubgen-test-path',
113124
delete=False)
114125
assert os.path.isabs(handle.name)
@@ -125,9 +136,11 @@ def test_stubgen(testcase: DataDrivenTestCase) -> None:
125136
reset_importlib_caches()
126137
try:
127138
if testcase.name.endswith('_import'):
128-
generate_stub_for_module(name, out_dir, quiet=True)
139+
generate_stub_for_module(name, out_dir, quiet=True,
140+
no_import=options.no_import,
141+
include_private=options.include_private)
129142
else:
130-
generate_stub(path, out_dir)
143+
generate_stub(path, out_dir, include_private=options.include_private)
131144
a = load_output(out_dir)
132145
except CompileError as e:
133146
a = e.messages

test-data/unit/stubgen.test

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,25 +125,52 @@ class A: ...
125125
[out]
126126
class A: ...
127127

128-
[case testPrivateFunction]
128+
[case testSkipPrivateFunction]
129129
def _f(): ...
130130
def g(): ...
131131
[out]
132132
def g(): ...
133133

134-
[case testPrivateMethod]
134+
[case testIncludePrivateFunction]
135+
# flags: --include-private
136+
def _f(): ...
137+
def g(): ...
138+
[out]
139+
def _f(): ...
140+
def g(): ...
141+
142+
[case testSkipPrivateMethod]
135143
class A:
136144
def _f(self): ...
137145
[out]
138146
class A: ...
139147

140-
[case testPrivateVar]
148+
[case testIncludePrivateMethod]
149+
# flags: --include-private
150+
class A:
151+
def _f(self): ...
152+
[out]
153+
class A:
154+
def _f(self): ...
155+
156+
[case testSkipPrivateVar]
141157
_x = 1
142158
class A:
143159
_y = 1
144160
[out]
145161
class A: ...
146162

163+
[case testIncludePrivateVar]
164+
# flags: --include-private
165+
_x = 1
166+
class A:
167+
_y = 1
168+
[out]
169+
_x = ... # type: int
170+
171+
class A:
172+
_y = ... # type: int
173+
147174
[case testSpecialInternalVar]
148175
__all__ = []
149176
__author__ = ''
@@ -309,14 +336,30 @@ class A:
309336
x = ... # type: int
310337
def f(self): ...
311338

312-
[case testMultiplePrivateDefs]
339+
[case testSkipMultiplePrivateDefs]
340+
class A: ...
341+
_x = 1
342+
_y = 1
343+
_z = 1
344+
class C: ...
345+
[out]
346+
class A: ...
347+
class C: ...
348+
349+
[case testIncludeMultiplePrivateDefs]
350+
# flags: --include-private
313351
class A: ...
314352
_x = 1
315353
_y = 1
316354
_z = 1
317355
class C: ...
318356
[out]
319357
class A: ...
358+
359+
_x = ... # type: int
360+
_y = ... # type: int
361+
_z = ... # type: int
362+
320363
class C: ...
321364

322365
[case testIncludeFromImportIfInAll_import]
@@ -396,6 +439,16 @@ class A:
396439
[out]
397440
class A: ...
398441

442+
[case testIncludePrivateProperty]
443+
# flags: --include-private
444+
class A:
445+
@property
446+
def _foo(self): ...
447+
[out]
448+
class A:
449+
@property
450+
def _foo(self): ...
451+
399452
[case testSkipPrivateStaticAndClassMethod]
400453
class A:
401454
@staticmethod
@@ -405,6 +458,20 @@ class A:
405458
[out]
406459
class A: ...
407460

461+
[case testIncludePrivateStaticAndClassMethod]
462+
# flags: --include-private
463+
class A:
464+
@staticmethod
465+
def _foo(): ...
466+
@classmethod
467+
def _bar(cls): ...
468+
[out]
469+
class A:
470+
@staticmethod
471+
def _foo(): ...
472+
@classmethod
473+
def _bar(cls): ...
474+
408475
[case testNamedtuple]
409476
import collections, x
410477
X = collections.namedtuple('X', ['a', 'b'])

0 commit comments

Comments
 (0)