From 1c4343be87a78e2e9a684d3bd70c648907a274cf Mon Sep 17 00:00:00 2001 From: tristanlatr Date: Wed, 1 Nov 2023 13:51:15 -0400 Subject: [PATCH] It's possible to re-export stuff from a class. --- pydoctor/astbuilder.py | 7 ++--- pydoctor/model.py | 18 +++++++----- pydoctor/templatewriter/pages/__init__.py | 2 +- pydoctor/test/test_astbuilder.py | 34 +++++++++++++++++++---- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/pydoctor/astbuilder.py b/pydoctor/astbuilder.py index 64e4d8879..58a513ef6 100644 --- a/pydoctor/astbuilder.py +++ b/pydoctor/astbuilder.py @@ -186,15 +186,14 @@ def _handleReExport(info:'ReExport', elsewhere:Collection['ReExport']) -> None: target = info.target as_name = info.as_name target_parent = target.parent - assert isinstance(target_parent, model.Module) # Remember that this name is re-exported - target_parent.elsewhere_contents[target.name] = target + target_parent.exported[target.name] = target extra_msg = '' for e in elsewhere: - e.new_parent.elsewhere_contents[e.as_name] = target + e.new_parent.exported[e.as_name] = target if not extra_msg: extra_msg += ', also available at ' @@ -325,7 +324,7 @@ def processReExports(system:'model.System') -> None: f"because {target.name!r} is already exported in public module {target.parent.fullName()!r}") for e in _exports: - e.new_parent.elsewhere_contents[e.as_name] = target + e.new_parent.exported[e.as_name] = target continue diff --git a/pydoctor/model.py b/pydoctor/model.py index d32cf4189..4d4746ef9 100644 --- a/pydoctor/model.py +++ b/pydoctor/model.py @@ -419,6 +419,17 @@ class CanContainImportsDocumentable(Documentable): def setup(self) -> None: super().setup() self._localNameToFullName_map: Dict[str, str] = {} + """ + Mapping from local names to fullnames: Powers name resolving. + """ + + self.exported: Dict[str, 'Documentable'] = {} + """ + When pydoctor re-export objects, it leaves references to object in this dict + so they can still be listed in childtable of origin modules or classes. This attribute belongs + to the "view model" part of Documentable interface and should only be used to present + links to these objects. Not to do name resolving. + """ def isNameDefined(self, name: str) -> bool: name = name.split('.')[0] @@ -479,13 +490,6 @@ def setup(self) -> None: self._docformat: Optional[str] = None self.imports: List[Import] = [] - self.elsewhere_contents: Dict[str, 'Documentable'] = {} - """ - When pydoctor re-export objects, it leaves references to object in this dict - so they can still be listed in childtable of origin modules. This attribute belongs - to the "view model" part of Documentable interface and should only be used to present - links to these objects. Not to do name resolving. - """ def _localNameToFullName(self, name: str) -> str: if name in self.contents: diff --git a/pydoctor/templatewriter/pages/__init__.py b/pydoctor/templatewriter/pages/__init__.py index 33b838d7f..3a33f27f1 100644 --- a/pydoctor/templatewriter/pages/__init__.py +++ b/pydoctor/templatewriter/pages/__init__.py @@ -407,7 +407,7 @@ def extras(self) -> List["Flattenable"]: def _iter_reexported_members(self, predicate: Optional[Callable[[model.Documentable], bool]]=None) -> Iterator[Tuple[str, model.Documentable]]: if not predicate: predicate = lambda v:True - return ((n,o) for n,o in self.ob.elsewhere_contents.items() if o.isVisible and predicate(o)) + return ((n,o) for n,o in self.ob.exported.items() if o.isVisible and predicate(o)) def children(self) -> Sequence[Union[model.Documentable, Tuple[str, model.Documentable]]]: return sorted(chain( diff --git a/pydoctor/test/test_astbuilder.py b/pydoctor/test/test_astbuilder.py index 53c5aab93..067e62a36 100644 --- a/pydoctor/test/test_astbuilder.py +++ b/pydoctor/test/test_astbuilder.py @@ -2915,7 +2915,7 @@ class cls2:... "moving 'pack._src.cls' into 'pack'\n" "not moving pack.src.cls2 into 'pack', because 'cls2' is already exported in public module 'pack.src'\n") - assert system.allobjects['pack.cls'] is system.allobjects['pack._src'].elsewhere_contents['cls'] # type:ignore + assert system.allobjects['pack.cls'] is system.allobjects['pack._src'].exported['cls'] # type:ignore @systemcls_param def test_do_not_reparent_to_existing_name(systemcls: Type[model.System], capsys:CapSys) -> None: @@ -2949,7 +2949,7 @@ class Cls:... "pack:1: duplicate Class 'pack.Slc'\n" "pack._src1:1: introduced by re-exporting Class 'pack._src1.Slc' into Package 'pack'\n") - assert system.allobjects['pack.Slc'] is system.allobjects['pack._src1'].elsewhere_contents['Slc'] # type:ignore + assert system.allobjects['pack.Slc'] is system.allobjects['pack._src1'].exported['Slc'] # type:ignore @systemcls_param def test_multiple_re_exports(systemcls: Type[model.System], capsys:CapSys) -> None: @@ -2979,8 +2979,8 @@ class Cls:... assert capsys.readouterr().out == ("moving 'pack.subpack.src.Cls' into 'pack', " "also available at 'pack.subpack.Cls'\n") - assert system.allobjects['pack.Cls'] is system.allobjects['pack.subpack'].elsewhere_contents['Cls'] # type:ignore - assert system.allobjects['pack.Cls'] is system.allobjects['pack.subpack.src'].elsewhere_contents['Cls'] # type:ignore + assert system.allobjects['pack.Cls'] is system.allobjects['pack.subpack'].exported['Cls'] # type:ignore + assert system.allobjects['pack.Cls'] is system.allobjects['pack.subpack.src'].exported['Cls'] # type:ignore @systemcls_param def test_multiple_re_exports_alias(systemcls: Type[model.System], capsys:CapSys) -> None: @@ -3007,5 +3007,27 @@ class DistinguishedName:... assert capsys.readouterr().out == ("moving 'pack.subpack.src.DistinguishedName' into 'pack' as 'DisName', " "also available at 'pack.DN'\n") - assert system.allobjects['pack.DisName'] is system.allobjects['pack'].elsewhere_contents['DN'] # type:ignore - assert system.allobjects['pack.DisName'] is system.allobjects['pack.subpack.src'].elsewhere_contents['DistinguishedName'] # type:ignore \ No newline at end of file + assert system.allobjects['pack.DisName'] is system.allobjects['pack'].exported['DN'] # type:ignore + assert system.allobjects['pack.DisName'] is system.allobjects['pack.subpack.src'].exported['DistinguishedName'] # type:ignore + +@systemcls_param +def test_re_export_method(systemcls: Type[model.System], capsys:CapSys) -> None: + src = '''\ + class Thing: + def method(self):... + method = Thing.method + ''' + subpack = '' + pack = ''' + from pack.subpack.src import method + __all__=['method'] + ''' + + system = systemcls() + builder = system.systemBuilder(system) + builder.addModuleString(pack, 'pack', is_package=True) + builder.addModuleString(subpack, 'subpack', is_package=True, parent_name='pack') + builder.addModuleString(src, 'src', parent_name='pack.subpack') + builder.buildModules() + assert capsys.readouterr().out == "moving 'pack.subpack.src.Thing.method' into 'pack'\n" +