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

Fixes for help and JIT imports #2586

Merged
merged 4 commits into from
May 14, 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
12 changes: 4 additions & 8 deletions hy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __getattr__(self, s):
# to be loaded if they're not needed.

_jit_imports = dict(
pyops=["hy.pyops", None],
read="hy.reader",
read_many="hy.reader",
mangle="hy.reader",
Expand All @@ -50,18 +51,13 @@ def __getattr__(self, s):


def __getattr__(k):
if k == "pyops":
global pyops
import hy.pyops

pyops = hy.pyops
return pyops

if k not in _jit_imports:
raise AttributeError(f"module {__name__!r} has no attribute {k!r}")

v = _jit_imports[k]
module, original_name = v if isinstance(v, list) else (v, k)
import importlib
module = importlib.import_module(module)

globals()[k] = getattr(importlib.import_module(module), original_name)
globals()[k] = getattr(module, original_name) if original_name else module
return globals()[k]
15 changes: 11 additions & 4 deletions hy/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,16 @@ def getdoc(object):
`inspect.getcomments` for an object defined in Hy code, which would try
to parse the Hy as Python. The implementation is based on Python
3.12.3's `getdoc`."""
result = pydoc._getdoc(object) or (
None
if inspect.getfile(object).endswith('.hy')
else inspect.getcomments(object))
result = pydoc._getdoc(object)
if not result:
can_get_comments = True
try:
file_path = inspect.getfile(object)
except TypeError:
None
else:
can_get_comments = not file_path.endswith('.hy')
if can_get_comments:
result = inspect.getcomments(object)
return result and re.sub('^ *\n', '', result.rstrip()) or ''
pydoc.getdoc = getdoc
4 changes: 1 addition & 3 deletions hy/reader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ def read_many(stream, filename="<string>", reader=None, skip_shebang=False):

.. warning::
Thanks to reader macros, reading can execute arbitrary code. Don't read untrusted
input.

"""
input."""

if isinstance(stream, str):
stream = StringIO(stream)
Expand Down
2 changes: 1 addition & 1 deletion hy/reader/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ def from_reader(cls, message, reader):

class PrematureEndOfInput(LexException):
"Raised when input ends unexpectedly during parsing."
pass
__module__ = 'hy'
2 changes: 2 additions & 0 deletions hy/reader/hy_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ class HyReader(Reader):
When ``use_current_readers`` is true, initialize this reader
with all reader macros from the calling module."""

__module__ = 'hy'

###
# Components necessary for Reader implementation
###
Expand Down
2 changes: 2 additions & 0 deletions hy/reader/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class Reader(metaclass=ReaderMeta):
A read-only (line, column) tuple indicating the current cursor
position of the source being read"""

__module__ = 'hy'

def __init__(self):
self._source = None
self._filename = None
Expand Down
5 changes: 2 additions & 3 deletions hy/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ class REPL(code.InteractiveConsole):
Note that as with :func:`code.interact`, changes to local variables inside the
REPL are not propagated back to the original scope."""

__module__ = 'hy'

def __init__(self, spy=False, spy_delimiter=('-' * 30), output_fn=None, locals=None, filename="<stdin>", allow_incomplete=True):

# Create a proper module for this REPL so that we can obtain it easily
Expand Down Expand Up @@ -460,6 +462,3 @@ def banner(self):
pyversion=platform.python_version(),
os=platform.system(),
)


REPL.__module__ = "hy" # Print as `hy.REPL` instead of `hy.repl.REPL`.
4 changes: 2 additions & 2 deletions tests/compilers/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from hy.compiler import hy_compile
from hy.errors import HyError, HyLanguageError
from hy.reader import read_many
from hy.reader.exceptions import LexException, PrematureEndOfInput
from hy.reader.exceptions import LexException


def _ast_spotcheck(arg, root, secondary):
Expand Down Expand Up @@ -466,7 +466,7 @@ def test_compile_error():

def test_for_compile_error():
"""Ensure we get compile error in tricky 'for' cases"""
with pytest.raises(PrematureEndOfInput) as excinfo:
with pytest.raises(hy.PrematureEndOfInput) as excinfo:
can_compile("(fn [] (for)")
assert excinfo.value.msg.startswith("Premature end of input")

Expand Down
5 changes: 2 additions & 3 deletions tests/importer/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from hy.errors import HyLanguageError, hy_exc_handler
from hy.importer import HyLoader
from hy.reader import read_many
from hy.reader.exceptions import PrematureEndOfInput


def test_basics():
Expand Down Expand Up @@ -183,7 +182,7 @@ def unlink(filename):

source.write_text("(setv a 11 (setv b (// 20 1))")

with pytest.raises(PrematureEndOfInput):
with pytest.raises(hy.PrematureEndOfInput):
reload(mod)

mod = sys.modules.get(TESTFN)
Expand Down Expand Up @@ -266,7 +265,7 @@ def test_filtered_importlib_frames(capsys):
spec = importlib.util.spec_from_loader(testLoader.name, testLoader)
mod = importlib.util.module_from_spec(spec)

with pytest.raises(PrematureEndOfInput) as execinfo:
with pytest.raises(hy.PrematureEndOfInput) as execinfo:
testLoader.exec_module(mod)

hy_exc_handler(execinfo.type, execinfo.value, execinfo.tb)
Expand Down
8 changes: 8 additions & 0 deletions tests/native_tests/other.hy
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,11 @@
(assert (= (pydoc.getdoc pydoc-hy.C2) "")))
; `pydoc` shouldn't try to get a comment from Hy code, since it
; doesn't know how to parse Hy.


(defn test-help-class-attr []
"Our tampering with `pydoc` shouldn't cause `help` to raise
`TypeError` on classes with non-method attributes."
(defclass C []
(setv attribute 1))
(help C))
8 changes: 3 additions & 5 deletions tests/native_tests/reader_macros.hy
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
types
contextlib [contextmanager]
hy.errors [HyMacroExpansionError]
hy.reader [HyReader]
hy.reader.exceptions [PrematureEndOfInput]

pytest)

Expand Down Expand Up @@ -55,7 +53,7 @@
(defn test-bad-reader-macro-name []
(with [(pytest.raises HyMacroExpansionError)]
(eval-module "(defreader :a-key '1)"))
(with [(pytest.raises PrematureEndOfInput)]
(with [(pytest.raises hy.PrematureEndOfInput)]
(eval-module "# _ 3")))

(defn test-get-macro []
Expand Down Expand Up @@ -123,7 +121,7 @@
(with [(pytest.raises hy.errors.HySyntaxError)]
(hy.read tag)))
;; but they should be installed in the module
(hy.eval '(setv reader (hy.reader.HyReader :use-current-readers True)) :module module)
(hy.eval '(setv reader (hy.HyReader :use-current-readers True)) :module module)
(setv reader module.reader)
(for [[s val] [["#r" 5]
["#test-read" 4]
Expand All @@ -132,7 +130,7 @@

;; passing a reader explicitly should work as expected
(with [module (temp-module "<test>")]
(setv reader (HyReader))
(setv reader (hy.HyReader))
(defn eval1 [s]
(hy.eval (hy.read s :reader reader) :module module))
(eval1 "(defreader fbaz 32)")
Expand Down
2 changes: 1 addition & 1 deletion tests/test_bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def req_err(x):
r' File "(?:<string>|string-[0-9a-f]+)", line 1\n'
r' \(print "\n'
r" \^\n"
r"hy.reader.exceptions.PrematureEndOfInput"
r"hy.PrematureEndOfInput"
)
assert re.search(peoi_re, error)

Expand Down
13 changes: 10 additions & 3 deletions tests/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest

from hy import PrematureEndOfInput
from hy.errors import hy_exc_handler
from hy.models import (
Bytes,
Expand All @@ -20,7 +21,7 @@
Symbol,
)
from hy.reader import read_many
from hy.reader.exceptions import LexException, PrematureEndOfInput
from hy.reader.exceptions import LexException


def tokenize(*args, **kwargs):
Expand Down Expand Up @@ -86,7 +87,7 @@ def test_lex_single_quote_err():
' File "<string>", line 1',
" '",
" ^",
"hy.reader.exceptions.PrematureEndOfInput: Premature end of input while attempting to parse one form",
"hy.PrematureEndOfInput: Premature end of input while attempting to parse one form",
],
)

Expand Down Expand Up @@ -660,7 +661,7 @@ def test_lex_exception_filtering(capsys):
' File "<string>", line 2',
" (foo",
" ^",
"hy.reader.exceptions.PrematureEndOfInput: Premature end of input while attempting to parse one form",
"hy.PrematureEndOfInput: Premature end of input while attempting to parse one form",
],
)

Expand Down Expand Up @@ -702,3 +703,9 @@ def test_shebang():
assert tokenize('#!/usr/bin/env hy\n5')
assert (tokenize('#!/usr/bin/env hy\n5', skip_shebang = True) ==
[Integer(5)])


def test_reader_class_reprs():
"Don't show internal modules in which these classes are defined."
assert repr(hy.Reader) == "<class 'hy.Reader'>"
assert repr(hy.HyReader) == "<class 'hy.HyReader'>"