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

BUG: add support for inspecting built-in modules #234

Merged
merged 2 commits into from
May 17, 2024
Merged
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
48 changes: 31 additions & 17 deletions src/wxc/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
if False:
# typecheck only
from collections.abc import Iterable # type: ignore [unreachable]
from typing import Any
from typing import Any, TypedDict

class FullDataDict(TypedDict):
source: str | None
version: str
in_stdlib: bool


# sorted by decreasing order of priority
Expand Down Expand Up @@ -111,9 +116,12 @@ def get_sourcefile(obj):
# this happens for instance with `math.sqrt`
# because inspect.getfile doesn't work on compiled code
# the second condition is met for os.fspath
if inspect.ismodule(obj) or is_builtin_func(obj):
raise
if isinstance(obj, property):
if (
inspect.ismodule(obj)
or is_builtin_func(obj)
or inspect.getmodule(obj) is builtins
or isinstance(obj, property)
):
raise
return get_sourcefile(inspect.getmodule(obj))
return file
Expand Down Expand Up @@ -153,22 +161,26 @@ def get_version(
raise LookupError(f"Could not determine version metadata from {package_name!r}")


def get_full_data(name: str) -> dict:
data = defaultdict(str)
def get_full_data(name: str) -> FullDataDict:
package_name, _, _ = name.partition(".")

objects = get_objects(name)
_d_source: str | None = None

for obj in reversed(objects):
try:
source = get_sourcefile(obj)
except RecursionError:
pass
except TypeError:
# as of Python 3.11, inspect.getfile doesn't have support for properties
# but we're not making this a hard failure in case it is added in the future
# and we fallback to finding out the sourcefile of the class itself
if isinstance(obj, property):
except TypeError as exc:
if "built-in module" in str(exc):
# see https://github.com/neutrinoceros/wxc/issues/233
_d_source = "built-in"
break
elif isinstance(obj, property):
# as of Python 3.11, inspect.getfile doesn't have support for properties
# but we're not making this a hard failure in case it is added in the future
# and we fallback to finding out the sourcefile of the class itself
continue
else:
raise
Expand All @@ -181,13 +193,15 @@ def get_full_data(name: str) -> dict:
source += f":{lineno}" if lineno else ""
break
finally:
data["source"] = source
_d_source = source

try:
data["version"] = get_version(package_name)
_d_version = get_version(package_name)
except LookupError:
pass

data["in_stdlib"] = package_name in sys.stdlib_module_names
_d_version = "unknown"

return data
return {
"source": _d_source,
"version": _d_version,
"in_stdlib": package_name in sys.stdlib_module_names,
}
8 changes: 4 additions & 4 deletions src/wxc/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ def main(argv: list[str] | None = None) -> int:
if args.full:
from rich import print

data["name"] = args.name
ver = f"version = {data.pop('version', 'unknown')}"
print("\n".join(f"{k} = {v}" for k, v in data.items()))
all_data = {**data, "name": args.name}
ver = f"version = {all_data['version']}"
print("\n".join(f"{k} = {v}" for k, v in all_data.items()))
builtin_print(ver)
return 0

Expand All @@ -137,7 +137,7 @@ def main(argv: list[str] | None = None) -> int:
builtin_print(data["version"])
return 0

if "source" not in data:
if data["source"] == "":
print_err(f"did not resolve source file for {args.name!r}")
return 1

Expand Down
2 changes: 2 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def test_finder(package_name):
assert "source" in imp

filename, _, line = imp["source"].partition(":")
if filename == "built-in":
return
p = Path(filename)
assert p.exists()
if not imp["in_stdlib"]:
Expand Down
10 changes: 0 additions & 10 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import sys
from importlib import import_module
from importlib.util import find_spec

Expand Down Expand Up @@ -26,15 +25,6 @@ def test_elementary_queries(capsys, package_name):
ret = main([package_name, "--version"])

out, err = capsys.readouterr()

if package_name == "math" and sys.platform.startswith("win"):
# rich may output an unspecified amount of newlines
# that don't actually affect the result visually
assert out.strip() == ""
assert err == "ERROR failed to locate source data.\n"
assert ret != 0
return

assert out != "unknown"
assert err == ""
assert ret == 0
Expand Down
2 changes: 1 addition & 1 deletion tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_empty_module_query(fake_module):
data = get_full_data(name)
filename, _, _ = data["source"].partition(":")
assert Path(syspath, name) in Path(filename).parents
assert "version" not in data
assert data["version"] == "unknown"

template.validate(data)

Expand Down