From 62c2fee80c8621746956a283788289fbf6232088 Mon Sep 17 00:00:00 2001 From: Alexey Khudyakov Date: Tue, 29 Sep 2026 14:45:36 +0300 Subject: [PATCH 1/2] Failing test for evalPyFunction --- test/TST/Run.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/TST/Run.hs b/test/TST/Run.hs index 34beb5f..63c51d7 100644 --- a/test/TST/Run.hs +++ b/test/TST/Run.hs @@ -186,6 +186,10 @@ testEvalExec = testGroup "eval/exec" assert m_hs.a == 12 assert m_hs.b == 'asd' |] + , testCase "evalPyFunction" $ runPy $ do + let n = 2 :: Int + x <- fromPy =<< evalPyFunction Temp [pycode| return 100 * n_hs |] + liftIO $ Just (200::Int) @=? x ] testAsync :: TestTree From f9f95ec34e5d148a3f1ac286534e68d4f98e0a03 Mon Sep 17 00:00:00 2001 From: Alexey Khudyakov Date: Mon, 5 Oct 2026 21:41:47 +0300 Subject: [PATCH 2/2] Introduce PyQuoteFun to make evalPyFunction usable --- src/Python/Inline/Eval.hs | 1 + src/Python/Inline/Monad/QQ.hs | 3 ++- src/Python/Inline/QQ.hs | 15 +++++++++++++++ src/Python/Internal/Eval.hs | 6 +++--- src/Python/Internal/EvalQQ.hs | 7 ++++--- src/Python/Internal/Types.hs | 13 +++++++++++++ test/TST/Run.hs | 2 +- 7 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/Python/Inline/Eval.hs b/src/Python/Inline/Eval.hs index 8141ffb..759fc83 100644 --- a/src/Python/Inline/Eval.hs +++ b/src/Python/Inline/Eval.hs @@ -10,6 +10,7 @@ module Python.Inline.Eval , evalPyFunction -- * Source code , PyQuote(..) + , PyQuoteFun(..) , Code , codeFromText , codeFromString diff --git a/src/Python/Inline/Monad/QQ.hs b/src/Python/Inline/Monad/QQ.hs index 710e411..2d42caa 100644 --- a/src/Python/Inline/Monad/QQ.hs +++ b/src/Python/Inline/Monad/QQ.hs @@ -9,6 +9,7 @@ module Python.Inline.Monad.QQ , pye , pyf , pycode + , pyfun ) where @@ -17,7 +18,7 @@ import Language.Haskell.TH.Quote import Python.Internal.EvalQQ import Python.Internal.Eval import Python.Inline.Monad -import Python.Inline.QQ (pycode) +import Python.Inline.QQ (pycode,pyfun) diff --git a/src/Python/Inline/QQ.hs b/src/Python/Inline/QQ.hs index 11e3dcd..f8dfb69 100644 --- a/src/Python/Inline/QQ.hs +++ b/src/Python/Inline/QQ.hs @@ -43,6 +43,7 @@ module Python.Inline.QQ , pye , pyf , pycode + , pyfun ) where import Language.Haskell.TH.Quote @@ -122,3 +123,17 @@ pycode = QuasiQuoter , quoteType = error "quoteType" , quoteDec = error "quoteDec" } + +-- | Create quote of python code suitable for use with +-- 'Python.Inline.Eval.exec' +-- +-- It creates value of type @PyQuoteFun@ +-- +-- @since 0.3@ +pyfun :: QuasiQuoter +pyfun = QuasiQuoter + { quoteExp = \txt -> expQQ Fun txt + , quotePat = error "quotePat" + , quoteType = error "quoteType" + , quoteDec = error "quoteDec" + } diff --git a/src/Python/Internal/Eval.hs b/src/Python/Internal/Eval.hs index 2254498..f4b8aff 100644 --- a/src/Python/Internal/Eval.hs +++ b/src/Python/Internal/Eval.hs @@ -1104,10 +1104,10 @@ exec globals locals q = runProgram $ do -- @since 0.3 evalPyFunction :: Namespace global - => global -- ^ Global variables - -> PyQuote -- ^ Source code + => global -- ^ Global variables + -> PyQuoteFun -- ^ Source code -> Py PyObject -evalPyFunction globals (PyQuote code binder) = runProgram $ do +evalPyFunction globals (PyQuoteFun code binder) = runProgram $ do p_locals <- takeOwnership =<< progPy basicNewDict p_kwargs <- takeOwnership =<< progPy basicNewDict progPy $ do diff --git a/src/Python/Internal/EvalQQ.hs b/src/Python/Internal/EvalQQ.hs index 59dc22f..8297309 100644 --- a/src/Python/Internal/EvalQQ.hs +++ b/src/Python/Internal/EvalQQ.hs @@ -116,9 +116,10 @@ expQQ mode qq_src = do ] src_eval = prepareForEval mode antis src -- - [| PyQuote ($(TH.lift $ codeFromString src_eval)) - (mconcat $(TH.listE args)) - |] + case mode of + Eval -> [| PyQuote ($(TH.lift $ codeFromString src_eval)) (mconcat $(TH.listE args)) |] + Exec -> [| PyQuote ($(TH.lift $ codeFromString src_eval)) (mconcat $(TH.listE args)) |] + Fun -> [| PyQuoteFun ($(TH.lift $ codeFromString src_eval)) (mconcat $(TH.listE args)) |] antiSuffix :: String diff --git a/src/Python/Internal/Types.hs b/src/Python/Internal/Types.hs index dadafce..91a6730 100644 --- a/src/Python/Internal/Types.hs +++ b/src/Python/Internal/Types.hs @@ -20,6 +20,7 @@ module Python.Internal.Types , pyIO -- ** Python code wrappers , PyQuote(..) + , PyQuoteFun(..) , Code(..) , codeFromText , codeFromString @@ -147,6 +148,18 @@ data PyQuote = PyQuote } +-- | Quasiquoted body of python function which could be used by +-- 'Python.Inline.Eval.evalPyFunction'. Function body must be +-- wrapped in @_inline_python_@ function which quasiquoter +-- 'Python.Inline.QQ.pyfun' does automatically. +-- +-- @since 0.3@ +data PyQuoteFun = PyQuoteFun + { code :: !Code + , binder :: !DictBinder + } + + -- | UTF-8 encoded python source code. -- -- @since 0.2@ diff --git a/test/TST/Run.hs b/test/TST/Run.hs index 63c51d7..6bb10c7 100644 --- a/test/TST/Run.hs +++ b/test/TST/Run.hs @@ -188,7 +188,7 @@ testEvalExec = testGroup "eval/exec" |] , testCase "evalPyFunction" $ runPy $ do let n = 2 :: Int - x <- fromPy =<< evalPyFunction Temp [pycode| return 100 * n_hs |] + x <- fromPy =<< evalPyFunction Temp [pyfun| return 100 * n_hs |] liftIO $ Just (200::Int) @=? x ]