Skip to content
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

Add support for type alias statements #771

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ This is the last major release to support Python 3.7.

* Drop support for Python 3.6.
* Add support for Python 3.12 and Python 3.13.
* Add support for the ``type`` statement introduced in Python 3.12.
* Astor is no longer a requirement starting at Python 3.9.
* `ExtRegistrar.register_post_processor()` now supports a `priority` argument that is an int.
Highest priority callables will be called first during post-processing.
Expand Down
29 changes: 22 additions & 7 deletions pydoctor/astbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,18 +553,22 @@
expr: Optional[ast.expr],
lineno: int,
augassign:Optional[ast.operator],
typealiasdef:bool|None=None,
) -> None:
if target in MODULE_VARIABLES_META_PARSERS:
# This is metadata, not a variable that needs to be documented,
# and therefore doesn't need an Attribute instance.
return
default_kind = (model.DocumentableKind.VARIABLE
if not typealiasdef else
model.DocumentableKind.TYPE_ALIAS)
parent = self.builder.current
obj = parent.contents.get(target)
if obj is None:
if augassign:
return
obj = self.builder.addAttribute(name=target,
kind=model.DocumentableKind.VARIABLE,
kind=default_kind,
parent=parent)

# If it's not an attribute it means that the name is already denifed as function/class
Expand All @@ -586,7 +590,7 @@
obj.setLineNumber(lineno)

self._handleConstant(obj, annotation, expr, lineno,
model.DocumentableKind.VARIABLE)
defaultKind=default_kind)
self._storeAttrValue(obj, expr, augassign)
self._storeCurrentAttr(obj, augassign)

Expand All @@ -596,11 +600,14 @@
expr: Optional[ast.expr],
lineno: int,
augassign:Optional[ast.operator],
typealiasdef:bool,
) -> None:
module = self.builder.current
assert isinstance(module, model.Module)
if not _handleAliasing(module, target, expr):
self._handleModuleVar(target, annotation, expr, lineno, augassign=augassign)
self._handleModuleVar(target, annotation, expr, lineno,
augassign=augassign,
typealiasdef=typealiasdef)

def _handleClassVar(self,
name: str,
Expand Down Expand Up @@ -724,16 +731,19 @@

def _handleAssignment(self,
targetNode: ast.expr,
annotation: Optional[ast.expr],
expr: Optional[ast.expr],
annotation: ast.expr|None,
expr: ast.expr|None,
lineno: int,
augassign:Optional[ast.operator]=None,
augassign:ast.operator|None=None,
typealiasdef:bool=False,
) -> None:
if isinstance(targetNode, ast.Name):
target = targetNode.id
scope = self.builder.current
if isinstance(scope, model.Module):
self._handleAssignmentInModule(target, annotation, expr, lineno, augassign=augassign)
self._handleAssignmentInModule(target, annotation, expr, lineno,
augassign=augassign,
typealiasdef=typealiasdef)
elif isinstance(scope, model.Class):
if augassign or not self._handleOldSchoolMethodDecoration(target, expr):
self._handleAssignmentInClass(target, annotation, expr, lineno, augassign=augassign)
Expand Down Expand Up @@ -766,6 +776,11 @@
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
annotation = unstring_annotation(node.annotation, self.builder.current)
self._handleAssignment(node.target, annotation, node.value, node.lineno)

def visit_TypeAlias(self, node: ast.TypeAlias) -> None:
if isinstance(node.name, ast.Name):
self._handleAssignment(node.name, None, node.value,

Check warning on line 782 in pydoctor/astbuilder.py

View check run for this annotation

Codecov / codecov/patch

pydoctor/astbuilder.py#L782

Added line #L782 was not covered by tests
node.lineno, typealiasdef=True)

def visit_AugAssign(self, node:ast.AugAssign) -> None:
self._handleAssignment(node.target, None, node.value,
Expand Down
31 changes: 23 additions & 8 deletions pydoctor/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ class LineFromAst(int):
class LineFromDocstringField(int):
"Simple L{int} wrapper for linenumbers coming from docstrings."

class AttributeValueDisplay(Enum):
HIDDEN = 0
AS_CODE_BLOCK = 1
# Not used yet. Default values of Attrs-like classes' attributes will one day (and maybe others).
#INLINE = 2

class DocLocation(Enum):
OWN_PAGE = 1
PARENT_PAGE = 2
Expand Down Expand Up @@ -874,6 +880,7 @@ class Attribute(Inheritable):
# Work around the attributes of the same name within the System class.
_ModuleT = Module
_PackageT = Package
_AttributeT = Attribute

T = TypeVar('T')

Expand Down Expand Up @@ -906,7 +913,7 @@ def import_mod_from_file_location(module_full_name:str, path: Path) -> types.Mod
class System:
"""A collection of related documentable objects.

PyDoctor documents collections of objects, often the contents of a
Pydoctor documents collections of objects, often the contents of a
package.
"""

Expand All @@ -928,13 +935,6 @@ class System:
Additional list of extensions to load alongside default extensions.
"""

show_attr_value = (DocumentableKind.CONSTANT,
DocumentableKind.TYPE_VARIABLE,
DocumentableKind.TYPE_ALIAS)
"""
What kind of attributes we should display the value for?
"""

def __init__(self, options: Optional['Options'] = None):
self.allobjects: Dict[str, Documentable] = {}
self.rootobjects: List[_ModuleT] = []
Expand Down Expand Up @@ -1120,6 +1120,21 @@ def objectsOfType(self, cls: Union[Type['DocumentableT'], str]) -> Iterator['Doc
if isinstance(o, cls):
yield o

# What kind of attributes we should pydoctor display the value for?
_show_attr_value = set((DocumentableKind.CONSTANT,
DocumentableKind.TYPE_VARIABLE,
DocumentableKind.TYPE_ALIAS))

def showAttrValue(self, ob: _AttributeT) -> AttributeValueDisplay:
"""
Whether to display the value of the given attribute.
"""

if ob.kind not in self._show_attr_value or ob.value is None:
return AttributeValueDisplay.HIDDEN
# Attribute is a constant/type alias (with a value), then display it's value
return AttributeValueDisplay.AS_CODE_BLOCK

def privacyClass(self, ob: Documentable) -> PrivacyClass:
ob_fullName = ob.fullName()
cached_privacy = self._privacyClassCache.get(ob_fullName)
Expand Down
19 changes: 13 additions & 6 deletions pydoctor/templatewriter/pages/attributechild.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from twisted.web.iweb import ITemplateLoader
from twisted.web.template import Tag, renderer, tags

from pydoctor.model import Attribute
from pydoctor.model import Attribute, AttributeValueDisplay, DocumentableKind
from pydoctor import epydoc2stan
from pydoctor.templatewriter import TemplateElement, util
from pydoctor.templatewriter.pages import format_decorators
Expand Down Expand Up @@ -55,9 +55,13 @@ def decorator(self, request: object, tag: Tag) -> "Flattenable":

@renderer
def attribute(self, request: object, tag: Tag) -> "Flattenable":
attr: List["Flattenable"] = [tags.span(self.ob.name, class_='py-defname')]
is_type_alias = self.ob.kind is DocumentableKind.TYPE_ALIAS
attr: List["Flattenable"] = []
if is_type_alias:
attr += [tags.span('type', class_='py-keyword'), ' ',]
attr += [tags.span(self.ob.name, class_='py-defname')]
_type = self.docgetter.get_type(self.ob)
if _type:
if _type and not is_type_alias:
attr.extend([': ', _type])
return attr

Expand All @@ -78,7 +82,10 @@ def functionBody(self, request: object, tag: Tag) -> "Flattenable":

@renderer
def constantValue(self, request: object, tag: Tag) -> "Flattenable":
if self.ob.kind not in self.ob.system.show_attr_value or self.ob.value is None:
showval = self.ob.system.showAttrValue(self.ob)
if showval is AttributeValueDisplay.HIDDEN:
return tag.clear()
# Attribute is a constant/type alias (with a value), then display it's value
return epydoc2stan.format_constant_value(self.ob)
elif showval is AttributeValueDisplay.AS_CODE_BLOCK:
return epydoc2stan.format_constant_value(self.ob)
else:
assert False
16 changes: 16 additions & 0 deletions pydoctor/test/test_astbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pydoctor import epydoc2stan
from pydoctor.epydoc.markup import DocstringLinker, ParsedDocstring
from pydoctor.options import Options
from pydoctor.astutils import unparse
from pydoctor.stanutils import flatten, html2stan, flatten_text
from pydoctor.epydoc.markup.epytext import Element, ParsedEpytextDocstring
from pydoctor.epydoc2stan import format_summary, get_parsed_type
Expand Down Expand Up @@ -2142,6 +2143,21 @@ def __init__(self):
assert mod.contents['F'].contents['Pouet'].kind == model.DocumentableKind.INSTANCE_VARIABLE
assert mod.contents['F'].contents['Q'].kind == model.DocumentableKind.INSTANCE_VARIABLE

@pytest.mark.skipif(sys.version_info < (3,12), reason='Type variable introduced in Python 3.12')
@systemcls_param
def test_type_alias_definition(systemcls: Type[model.System]) -> None:
src = '''
import typing as t
type One = t.Literal['1', 1]
'''
mod = fromText(src, systemcls=systemcls)
attr = mod.contents['One']
assert isinstance(attr, model.Attribute)
assert attr.kind == model.DocumentableKind.TYPE_ALIAS
assert attr.value
assert unparse(attr.value).strip() == "t.Literal['1', 1]"


@systemcls_param
def test_typevartuple(systemcls: Type[model.System]) -> None:
"""
Expand Down
Loading