diff --git a/src/Python/Inline/Eval.hs b/src/Python/Inline/Eval.hs index a9ee307..26b7e83 100644 --- a/src/Python/Inline/Eval.hs +++ b/src/Python/Inline/Eval.hs @@ -1,5 +1,6 @@ -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} -- | -- Interface to python's @eval@ and @exec@ which gives programmer -- control over local and global variables. @@ -8,6 +9,7 @@ module Python.Inline.Eval eval , exec , evalPyFunction + , createModule -- * Source code , PyQuote(..) , PyQuoteFun(..) @@ -26,6 +28,7 @@ module Python.Inline.Eval import Data.ByteString qualified as BS import Data.Text (Text) +import Data.Text qualified as T import Data.Text.Encoding qualified as T import Language.C.Inline qualified as C import Language.C.Inline.Unsafe qualified as CU @@ -35,7 +38,6 @@ import Python.Internal.Eval import Python.Internal.Program import Python.Inline.Literal - ---------------------------------------------------------------- C.context (C.baseCtx <> pyCtx) C.include "" @@ -59,3 +61,25 @@ bindVar name a = DictBinder $ \p_dict -> runProgram $ do 0 -> pure () _ -> mustThrowPyError + +-- | Create python module from given quote. This function creates new +-- module using @importlib@ machinery and executes quote in context +-- of this module. +createModule + :: Text + -> PyQuote + -> Py Module +createModule name quote = do + -- It would be nice to use quasiquotes but linker chokes on them. + -- We have to this hard way + mdl <- Module <$> evalPyFunction Temp PyQuoteFun + { code = codeFromText $ T.unlines + [ "def _inline_python_(name):" + , " import types" + , " return types.ModuleType(name)" + ] + , binder = bindVar "name" name + } + mdl <$ exec mdl mdl quote + + diff --git a/src/Python/Inline/Monad/QQ.hs b/src/Python/Inline/Monad/QQ.hs index 2d42caa..40b046c 100644 --- a/src/Python/Inline/Monad/QQ.hs +++ b/src/Python/Inline/Monad/QQ.hs @@ -4,12 +4,15 @@ -- "Python.Inline.QQ" but they produce splices which are polymorphic -- in 'MonadPy'. For 'Py' in particular they behave identically. module Python.Inline.Monad.QQ - ( pymain + ( -- * Evalution in @Py@ monad + pymain , py_ , pye , pyf + -- * Creation of @PyQuote@ , pycode , pyfun + , pySource ) where @@ -18,7 +21,7 @@ import Language.Haskell.TH.Quote import Python.Internal.EvalQQ import Python.Internal.Eval import Python.Inline.Monad -import Python.Inline.QQ (pycode,pyfun) +import Python.Inline.QQ (pycode, pyfun, pySource) diff --git a/src/Python/Inline/QQ.hs b/src/Python/Inline/QQ.hs index f8dfb69..0b6d443 100644 --- a/src/Python/Inline/QQ.hs +++ b/src/Python/Inline/QQ.hs @@ -38,18 +38,24 @@ -- globals and locals. If tighter control over variables scope is -- required APIs from "Python.Inline.Eval" should be used instead. module Python.Inline.QQ - ( pymain + ( -- * Evalution in @Py@ monad + pymain , py_ , pye , pyf + -- * Creation of @PyQuote@ + -- $PyQuote , pycode , pyfun + , pySource ) where import Language.Haskell.TH.Quote +import Language.Haskell.TH.Syntax qualified as TH import Python.Internal.EvalQQ import Python.Internal.Eval +import Python.Internal.Types -- | Evaluate sequence of python statements. It uses python's @exec@. @@ -109,11 +115,16 @@ pyf = QuasiQuoter , quoteDec = error "quoteDec" } --- | Create quote of python code suitable for use with --- 'Python.Inline.Eval.exec', 'Python.Inline.Eval.eval', --- 'Python.Inline.Eval.evalPyFunction'. +-- $PyQuote -- --- It creates value of type 'Python.Inline.Eval.PyQuote'. +-- 'PyQuote' wraps python code and bould haskell variables. It could +-- be evaluated using 'Python.Inline.Eval.exec', +-- 'Python.Inline.Eval.eval', 'Python.Inline.Eval.evalPyFunction'. + + +-- | Create quote of python code. It captures haskell variables in the +-- same way as rest of quasiquotes and creates value of type +-- 'Python.Inline.Eval.PyQuote'. -- -- @since 0.2@ pycode :: QuasiQuoter @@ -137,3 +148,15 @@ pyfun = QuasiQuoter , quoteType = error "quoteType" , quoteDec = error "quoteDec" } + +-- | Create value of type 'PyQuote' from file. Created quote doesn't +-- capture any variables. +-- +-- @since 0.3 +pySource :: FilePath -> TH.Q TH.Exp +pySource path = do + TH.addDependentFile path + [| PyQuote { code = $(TH.lift =<< TH.runIO (codeFromString <$> readFile path)) + , binder = mempty + } |] + diff --git a/test/TST/Run.hs b/test/TST/Run.hs index 6bb10c7..9e49837 100644 --- a/test/TST/Run.hs +++ b/test/TST/Run.hs @@ -8,6 +8,7 @@ import Control.Exception import Control.Monad import Control.Monad.IO.Class import Data.Map.Strict qualified as Map +import Data.Text qualified as T import Test.Tasty import Test.Tasty.HUnit import Python.Inline @@ -190,6 +191,12 @@ testEvalExec = testGroup "eval/exec" let n = 2 :: Int x <- fromPy =<< evalPyFunction Temp [pyfun| return 100 * n_hs |] liftIO $ Just (200::Int) @=? x + , testCase "createModule" $ runPy $ do + m <- createModule (T.pack "foo") [pycode||] + [py_| + import types + assert isinstance(m_hs, types.ModuleType) + |] ] testAsync :: TestTree