Skip to content

Commit

Permalink
chore: Comment the code
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Sep 21, 2023
1 parent da0dabe commit 039a702
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 95 deletions.
14 changes: 14 additions & 0 deletions src/griffe/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def _load_packages(
allow_inspection: bool = True,
store_source: bool = True,
) -> GriffeLoader:
# Create a single loader.
loader = GriffeLoader(
extensions=extensions,
search_paths=search_paths,
Expand All @@ -82,6 +83,8 @@ def _load_packages(
allow_inspection=allow_inspection,
store_source=store_source,
)

# Load each package.
for package in packages:
if not package:
logger.debug("Empty package name, continuing")
Expand All @@ -94,6 +97,8 @@ def _load_packages(
except ImportError as error:
logger.exception(f"Tried but could not import package {package}: {error}") # noqa: TRY401
logger.info("Finished loading packages")

# Resolve aliases.
if resolve_aliases:
logger.info("Starting alias resolution")
unresolved, iterations = loader.resolve_aliases(implicit=resolve_implicit, external=resolve_external)
Expand Down Expand Up @@ -323,6 +328,7 @@ def dump(
Returns:
`0` for success, `1` for failure.
"""
# Prepare options.
per_package_output = False
if isinstance(output, str) and output.format(package="package") != output:
per_package_output = True
Expand All @@ -337,6 +343,7 @@ def dump(
logger.exception(str(error)) # noqa: TRY401
return 1

# Load packages.
loader = _load_packages(
packages,
extensions=loaded_extensions,
Expand All @@ -351,6 +358,7 @@ def dump(
)
data_packages = loader.modules_collection.members

# Serialize and dump packages.
started = datetime.now(tz=timezone.utc)
if per_package_output:
for package_name, data in data_packages.items():
Expand Down Expand Up @@ -394,6 +402,7 @@ def check(
Returns:
`0` for success, `1` for failure.
"""
# Prepare options.
search_paths = list(search_paths) if search_paths else []

try:
Expand All @@ -410,6 +419,7 @@ def check(
logger.exception(str(error)) # noqa: TRY401
return 1

# Load old and new version of the package.
old_package = load_git(
against_path,
ref=against,
Expand All @@ -436,6 +446,7 @@ def check(
allow_inspection=allow_inspection,
)

# Find and display API breakages.
breakages = list(find_breaking_changes(old_package, new_package))

colorama.deinit()
Expand All @@ -460,11 +471,13 @@ def main(args: list[str] | None = None) -> int:
Returns:
An exit code.
"""
# Parse arguments.
parser = get_parser()
opts: argparse.Namespace = parser.parse_args(args)
opts_dict = opts.__dict__
subcommand = opts_dict.pop("subcommand")

# Initialize logging.
log_level = opts_dict.pop("log_level", DEFAULT_LOG_LEVEL)
try:
level = getattr(logging, log_level)
Expand All @@ -478,6 +491,7 @@ def main(args: list[str] | None = None) -> int:
else:
logging.basicConfig(format="%(levelname)-10s %(message)s", level=level)

# Run subcommand.
commands: dict[str, Callable[..., int]] = {"check": check, "dump": dump}
return commands[subcommand](**opts_dict)

Expand Down
113 changes: 87 additions & 26 deletions src/griffe/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,22 +526,37 @@ def relative_package_filepath(self) -> Path:
ValueError: When the relative path could not be computed.
"""
package_path = self.package.filepath

# Current "module" is a namespace package.
if isinstance(self.filepath, list):
# Current package is a namespace package.
if isinstance(package_path, list):
for pkg_path in package_path:
for self_path in self.filepath:
with suppress(ValueError):
return self_path.relative_to(pkg_path.parent)

# Current package is a regular package.
# NOTE: Technically it makes no sense to have a namespace package
# under a non-namespace one, so we should never enter this branch.
else:
for self_path in self.filepath:
with suppress(ValueError):
return self_path.relative_to(package_path.parent.parent)
raise ValueError

# Current package is a namespace package,
# and current module is a regular module or package.
if isinstance(package_path, list):
for pkg_path in package_path:
with suppress(ValueError):
return self.filepath.relative_to(pkg_path.parent)
raise ValueError

# Current package is a regular package,
# and current module is a regular module or package,
# try to compute the path relative to the parent folder
# of the package (search path).
return self.filepath.relative_to(package_path.parent.parent)

@property
Expand Down Expand Up @@ -643,17 +658,27 @@ def resolve(self, name: str) -> str:
Returns:
The resolved name.
"""
# Name is a member this object.
if name in self.members:
if self.members[name].is_alias:
return self.members[name].target_path # type: ignore[union-attr]
return self.members[name].path

# Name was imported.
if name in self.imports:
return self.imports[name]

# Name unknown and no more parent scope.
if self.parent is None:
# could be a built-in
raise NameResolutionError(f"{name} could not be resolved in the scope of {self.path}")

# Name is parent, non-module object.
# NOTE: possibly useless branch.
if name == self.parent.name and not self.parent.is_module:
return self.parent.path

# Recurse in parent.
return self.parent.resolve(name)

def as_dict(self, *, full: bool = False, **kwargs: Any) -> dict[str, Any]:
Expand Down Expand Up @@ -780,6 +805,8 @@ def __len__(self) -> int:
return 1

# SPECIAL PROXIES -------------------------------
# The following methods and properties exist on the target(s),
# but we must handle them in a special way.

@property
def kind(self) -> Kind:
Expand All @@ -790,16 +817,6 @@ def kind(self) -> Kind:
except (AliasResolutionError, CyclicAliasError):
return Kind.ALIAS

@property
def lineno(self) -> int | None:
"""The target lineno or the alias lineno."""
return self.final_target.lineno

@property
def endlineno(self) -> int | None:
"""The target endlineno or the alias endlineno."""
return self.final_target.endlineno

@property
def has_docstring(self) -> bool:
"""Whether this alias' target has a non-empty docstring."""
Expand Down Expand Up @@ -837,7 +854,46 @@ def modules_collection(self) -> ModulesCollection:
# no need to forward to the target
return self.parent.modules_collection # type: ignore[union-attr] # we assume there's always a parent

@cached_property
def members(self) -> dict[str, Object | Alias]: # noqa: D102
final_target = self.final_target

# We recreate aliases to maintain a correct hierarchy,
# and therefore correct paths. The path of an alias member
# should be the path of the alias plus the member's name,
# not the original member's path.
return {
name: Alias(name, target=member, parent=self, inherited=False)
for name, member in final_target.members.items()
}

@cached_property
def inherited_members(self) -> dict[str, Alias]: # noqa: D102
final_target = self.final_target

# We recreate aliases to maintain a correct hierarchy,
# and therefore correct paths. The path of an alias member
# should be the path of the alias plus the member's name,
# not the original member's path.
return {
name: Alias(name, target=member, parent=self, inherited=True)
for name, member in final_target.inherited_members.items()
}

# GENERIC OBJECT PROXIES --------------------------------
# The following methods and properties exist on the target(s).
# We first try to reach the final target, trigerring alias resolution errors
# and cyclic aliases errors early. We avoid recursing in the alias chain.

@property
def lineno(self) -> int | None:
"""The target lineno or the alias lineno."""
return self.final_target.lineno

@property
def endlineno(self) -> int | None:
"""The target endlineno or the alias endlineno."""
return self.final_target.endlineno

@property
def docstring(self) -> Docstring | None: # noqa: D102
Expand All @@ -847,14 +903,6 @@ def docstring(self) -> Docstring | None: # noqa: D102
def docstring(self, docstring: Docstring | None) -> None:
self.final_target.docstring = docstring

@cached_property
def members(self) -> dict[str, Object | Alias]: # noqa: D102
final_target = self.final_target
return {
name: Alias(name, target=member, parent=self, inherited=False)
for name, member in final_target.members.items()
}

@property
def labels(self) -> set[str]: # noqa: D102
return self.final_target.labels
Expand All @@ -874,14 +922,6 @@ def aliases(self) -> dict[str, Alias]: # noqa: D102
def member_is_exported(self, member: Object | Alias, *, explicitely: bool = True) -> bool: # noqa: D102
return self.final_target.member_is_exported(member, explicitely=explicitely)

@cached_property
def inherited_members(self) -> dict[str, Alias]: # noqa: D102
final_target = self.final_target
return {
name: Alias(name, target=member, parent=self, inherited=True)
for name, member in final_target.inherited_members.items()
}

def is_kind(self, kind: str | Kind | set[str | Kind]) -> bool: # noqa: D102
return self.final_target.is_kind(kind)

Expand Down Expand Up @@ -952,6 +992,9 @@ def del_member(self, key: str | Sequence[str]) -> None: # noqa: D102
return self.final_target.del_member(key)

# SPECIFIC MODULE/CLASS/FUNCTION/ATTRIBUTE PROXIES ---------------
# These methods and properties exist on targets of specific kind.
# We have to call the method/property on the first target, not the final one
# (which could be of another kind).

@property
def _filepath(self) -> Path | list[Path] | None:
Expand Down Expand Up @@ -1013,6 +1056,8 @@ def mro(self) -> list[Class]: # noqa: D102
return cast(Class, self.final_target).mro()

# SPECIFIC ALIAS METHOD AND PROPERTIES -----------------
# These methods and properties do not exist on targets,
# they are specific to aliases.

@property
def target(self) -> Object | Alias:
Expand Down Expand Up @@ -1040,6 +1085,12 @@ def final_target(self) -> Object:
This will iterate through the targets until a non-alias object is found.
"""
# Here we quickly iterate on the alias chain,
# remembering which path we've seen already to detect cycles.

# The cycle detection is needed because alias chains can be created
# as already resolved, and can contain cycles.

# using a dict as an ordered set
paths_seen: dict[str, None] = {}
target = self
Expand All @@ -1061,6 +1112,16 @@ def resolve_target(self) -> None:
and added to the collection.
CyclicAliasError: When the resolved target is the alias itself.
"""
# Here we try to resolve the whole alias chain recursively.
# We detect cycles by setting a "passed through" state variable
# on each alias as we pass through it. Passing a second time
# through an alias will raise a CyclicAliasError.

# If a single link of the chain cannot be resolved,
# the whole chain stays unresolved. This prevents
# bad surprises later, in code that checks if
# an alias is resolved by checking only
# the first link of the chain.
if self._passed_through:
raise CyclicAliasError([self.target_path])
self._passed_through = True
Expand Down
Loading

0 comments on commit 039a702

Please sign in to comment.