Kodiologist: Generally, this looks good.

- See `CONTRIBUTING.rst` for guidelines about commit messages, `NEWS`, and `AUTHORS`.
- In `test_importer`, import `read_str` and `hy_eval` directly from `hy`, since that's the interface you want to test.
- Define `eval_str` inside `test_eval`, since it's only used there.

Kodiologist: Also, could you describe the `eof` parameter of `read` in its docstring? Alternatively, `read` could perhaps be rewritten without it. The current implementation looks kind of weird.
jdanford: All fixes have been made!
Kodiologist: Cool. Don't forget about https://github.com/hylang/hy/pull/1369#issuecomment-320468126.
diff --git a/AUTHORS b/AUTHORS
index dcc9b0ce..7791140d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -80,3 +80,4 @@
 * Neil Lindquist <archer1mail@gmail.com
 * Hikaru Ikuta <woodrush924@gmail.com>
 * David Schaefer <david.schaefe@gmail.com>
+* Jordan Danford <jordandanford@gmail.com>
diff --git a/NEWS b/NEWS
index 6580965e..4a2a9e59 100644
--- a/NEWS
+++ b/NEWS
@@ -27,6 +27,10 @@ Changes from 0.13.0
    * `exec` now works under Python 2
    * No TypeError from multi-arity defn returning values evaluating to None
 
+   [ Misc. Improvements ]
+   * `read`, `read_str`, and `eval` are exposed and documented as top-level
+     functions in the `hy` module
+
 Changes from 0.12.1
 
    [ Language Changes ]
diff --git a/docs/language/interop.rst b/docs/language/interop.rst
index 433679cc..039d825c 100644
--- a/docs/language/interop.rst
+++ b/docs/language/interop.rst
@@ -131,5 +131,17 @@ argument::
     test()
     bar
 
+Evaluating strings of Hy code from Python
+-----------------------------------------
 
+Evaluating a string (or ``file`` object) containing a Hy expression requires
+two separate steps. First, use the ``read_str`` function (or ``read`` for a
+``file`` object) to turn the expression into a Hy model::
 
+    >>> import hy
+    >>> expr = hy.read_str("(- (/ (+ 1 3 88) 2) 8)")
+
+Then, use the ``eval`` function to evaluate it::
+
+    >>> hy.eval(expr)
+    38.0
diff --git a/hy/__init__.py b/hy/__init__.py
index a17ddbbe..6b986d19 100644
--- a/hy/__init__.py
+++ b/hy/__init__.py
@@ -10,3 +10,7 @@ from hy.models import HyExpression, HyInteger, HyKeyword, HyComplex, HyString, H
 
 import hy.importer  # NOQA
 # we import for side-effects.
+
+
+from hy.core.language import read, read_str  # NOQA
+from hy.importer import hy_eval as eval  # NOQA
diff --git a/hy/core/language.hy b/hy/core/language.hy
index 5f9378ad..4504b6ab 100644
--- a/hy/core/language.hy
+++ b/hy/core/language.hy
@@ -426,8 +426,8 @@
 
 (defn read [&optional [from-file sys.stdin]
                       [eof ""]]
-  "Read from input and returns a tokenized string.
-   Can take a given input buffer to read from"
+  "Read from input and returns a tokenized string. Can take a given input buffer
+   to read from, and a single byte as EOF (defaults to an empty string)"
   (setv buff "")
   (while True
     (setv inn (string (.readline from-file)))

Official support for evaluating strings of Hy code from Python
Is it possible to embed some hy code inside a python file? As opposed to having the whole file be full on hy?
diff --git a/tests/importer/test_importer.py b/tests/importer/test_importer.py
index 79b72daa..9fa5aa59 100644
--- a/tests/importer/test_importer.py
+++ b/tests/importer/test_importer.py
@@ -2,12 +2,14 @@
 # This file is part of Hy, which is free software licensed under the Expat
 # license. See the LICENSE.
 
+import hy
 from hy.importer import (import_file_to_module, import_buffer_to_ast,
                          MetaLoader, get_bytecode_path)
 from hy.errors import HyTypeError
 import os
 import ast
 import tempfile
+from fractions import Fraction
 
 
 def test_basics():
@@ -64,3 +66,20 @@ def test_import_autocompiles():
 
     os.remove(f.name)
     os.remove(get_bytecode_path(f.name))
+
+
+def test_eval():
+    def eval_str(s):
+        return hy.eval(hy.read_str(s))
+
+    assert eval_str('[1 2 3]') == [1, 2, 3]
+    assert eval_str('{"dog" "bark" "cat" "meow"}') == {
+        'dog': 'bark', 'cat': 'meow'}
+    assert eval_str('(, 1 2 3)') == (1, 2, 3)
+    assert eval_str('#{3 1 2}') == {1, 2, 3}
+    assert eval_str('1/2') == Fraction(1, 2)
+    assert eval_str('(.strip " fooooo   ")') == 'fooooo'
+    assert eval_str(
+        '(if True "this is if true" "this is if false")') == "this is if true"
+    assert eval_str('(list-comp (pow num 2) (num (range 100)) (= (% num 2) 1))') == [
+        pow(num, 2) for num in range(100) if num % 2 == 1]

pip install -e .
parse_log_pytest
pytest
pytest tox Pygments>=1.6 Sphinx sphinx_rtd_theme
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
appdirs==1.4.4
args==0.1.0
astor==0.8.1
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
Babel==2.11.0
certifi==2021.5.30
charset-normalizer==2.0.12
clint==0.5.1
distlib==0.3.9
docutils==0.18.1
filelock==3.4.1
-e git+https://github.com/hylang/hy.git@b6dceb0aa39f7b33dd70cec83fce3d08f68d947c#egg=hy
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
Jinja2==3.0.3
MarkupSafe==2.0.1
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
platformdirs==2.4.0
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
Pygments==2.14.0
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
pytz==2025.2
requests==2.27.1
rply==0.7.8
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-rtd-theme==2.0.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
tox==3.28.0
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
urllib3==1.26.20
virtualenv==20.17.1
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: hy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - appdirs==1.4.4
      - args==0.1.0
      - astor==0.8.1
      - babel==2.11.0
      - charset-normalizer==2.0.12
      - clint==0.5.1
      - distlib==0.3.9
      - docutils==0.18.1
      - filelock==3.4.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - platformdirs==2.4.0
      - pygments==2.14.0
      - pytz==2025.2
      - requests==2.27.1
      - rply==0.7.8
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-rtd-theme==2.0.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tox==3.28.0
      - urllib3==1.26.20
      - virtualenv==20.17.1
prefix: /opt/conda/envs/hy

tests/importer/test_importer.py::test_eval
tests/importer/test_importer.py::test_basics tests/importer/test_importer.py::test_stringer tests/importer/test_importer.py::test_imports tests/importer/test_importer.py::test_import_error_reporting tests/importer/test_importer.py::test_import_autocompiles
hy/__init__.py NEWS hy/core/language.hy AUTHORS docs/language/interop.rst
hy/__init__.py NEWS hy/core/language.hy AUTHORS docs/language/interop.rst