Skip to content
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
30 changes: 27 additions & 3 deletions src/Python/Inline/Eval.hs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -8,6 +9,7 @@ module Python.Inline.Eval
eval
, exec
, evalPyFunction
, createModule
-- * Source code
, PyQuote(..)
, PyQuoteFun(..)
Expand All @@ -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
Expand All @@ -35,7 +38,6 @@ import Python.Internal.Eval
import Python.Internal.Program
import Python.Inline.Literal


----------------------------------------------------------------
C.context (C.baseCtx <> pyCtx)
C.include "<inline-python.h>"
Expand All @@ -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


7 changes: 5 additions & 2 deletions src/Python/Inline/Monad/QQ.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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)



Expand Down
33 changes: 28 additions & 5 deletions src/Python/Inline/QQ.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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@.
Expand Down Expand Up @@ -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
Expand All @@ -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
} |]

7 changes: 7 additions & 0 deletions test/TST/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading