@@ -1454,13 +1454,153 @@ end
14541454Import a module `m`, or an attribute `k`, or a tuple of attributes.
14551455
14561456If several arguments are given, return the results of importing each one in a tuple.
1457+
1458+ See also: [`@pyimport_str`](@ref).
14571459"""
14581460pyimport (m) = pynew (errcheck (@autopy m C. PyImport_Import (m_)))
14591461pyimport ((m, k):: Pair ) = (m_ = pyimport (m); k_ = pygetattr (m_, k); pydel! (m_); k_)
14601462pyimport ((m, ks):: Pair{<:Any,<:Tuple} ) =
14611463 (m_ = pyimport (m); ks_ = map (k -> pygetattr (m_, k), ks); pydel! (m_); ks_)
14621464pyimport (m1, m2, ms... ) = map (pyimport, (m1, m2, ms... ))
14631465
1466+ """
1467+ pyimport"import numpy"
1468+ pyimport"import numpy as np"
1469+ pyimport"from numpy import array"
1470+ pyimport"from numpy import array as arr"
1471+ pyimport"from numpy import array, zeros"
1472+ pyimport"from numpy import array as arr, zeros as z"
1473+ pyimport"import numpy, scipy"
1474+
1475+ String macro that parses Python import syntax and generates equivalent Julia
1476+ code using [`pyimport()`](@ref). Each form generates `const` bindings in the
1477+ caller's scope.
1478+
1479+ Multiple lines are supported:
1480+ ```julia
1481+ pyimport\"\"\"
1482+ import numpy as np
1483+ from scipy import linalg, optimize
1484+ from os.path import join as pathjoin
1485+ \"\"\"
1486+
1487+ # Converted to:
1488+ const np = pyimport("numpy")
1489+ const linalg = pyimport("scipy" => "linalg")
1490+ const optimize = pyimport("scipy" => "optimize")
1491+ const pathjoin = pyimport("os.path" => "join")
1492+ ```
1493+
1494+ But multiline or grouped import statements are not supported:
1495+ ```julia
1496+ # These will throw an error
1497+ pyimport\"\"\"
1498+ from sys import (path,
1499+ version)
1500+ from sys import path, \
1501+ version
1502+ \"\"\"
1503+ ```
1504+
1505+ Relative imports are also not currently supported.
1506+ """
1507+ macro pyimport_str (s)
1508+ esc (_pyimport_parse (s))
1509+ end
1510+
1511+ function _pyimport_parse (s:: AbstractString )
1512+ lines = filter (! isempty, strip .(split (s, " \n " )))
1513+ isempty (lines) && throw (ArgumentError (" pyimport: empty import string" ))
1514+
1515+ # Check for line continuations
1516+ for line in lines
1517+ if contains (line, ' \\ ' ) || contains (line, ' (' )
1518+ throw (ArgumentError (" pyimport: line continuation with '\\ ' or '(' is not supported: $line " ))
1519+ end
1520+ end
1521+
1522+ if length (lines) == 1
1523+ _pyimport_parse_line (lines[1 ])
1524+ else
1525+ Expr (:block , [_pyimport_parse_line (line) for line in lines]. .. )
1526+ end
1527+ end
1528+
1529+ function _pyimport_parse_line (s:: AbstractString )
1530+ if startswith (s, " from " )
1531+ _pyimport_parse_from (s)
1532+ elseif startswith (s, " import " )
1533+ _pyimport_parse_import (s)
1534+ else
1535+ throw (ArgumentError (" pyimport: expected 'import ...' or 'from ... import ...', got: $s " ))
1536+ end
1537+ end
1538+
1539+ function _pyimport_parse_import (s:: AbstractString )
1540+ rest = strip (chopprefix (s, " import" ))
1541+ if isempty (rest)
1542+ throw (ArgumentError (" pyimport: missing module name after 'import'" ))
1543+ end
1544+
1545+ parts = split (rest, " ," ; keepempty= false )
1546+ exprs = Expr[]
1547+ for part in parts
1548+ part = strip (part)
1549+
1550+ # Check if there's an `as` clause
1551+ m = match (r" ^(\S +)\s +as\s +(\S +)$" , part)
1552+ if ! isnothing (m)
1553+ # `import numpy.linalg as la` binds la to the linalg submodule
1554+ modname = m[1 ]
1555+ alias = Symbol (m[2 ])
1556+ push! (exprs, :(const $ alias = pyimport ($ modname)))
1557+ else
1558+ modname = part
1559+ # `import numpy.linalg` binds numpy (top-level package)
1560+ # but first imports the submodule to ensure it's loaded
1561+ dotparts = split (modname, " ." )
1562+ alias = Symbol (dotparts[1 ])
1563+ if length (dotparts) == 1
1564+ push! (exprs, :(const $ alias = pyimport ($ modname)))
1565+ else
1566+ toplevel = dotparts[1 ]
1567+ push! (exprs, :(const $ alias = (pyimport ($ modname); pyimport ($ toplevel))))
1568+ end
1569+ end
1570+ end
1571+
1572+ length (exprs) == 1 ? exprs[1 ] : Expr (:block , exprs... )
1573+ end
1574+
1575+ function _pyimport_parse_from (s:: AbstractString )
1576+ m = match (r" ^from\s +(\S +)\s +import\s +(.+)$" , s)
1577+ if isnothing (m)
1578+ throw (ArgumentError (" pyimport: invalid from-import syntax: $s " ))
1579+ end
1580+
1581+ modname = m[1 ]
1582+ rest = strip (m[2 ])
1583+ if rest == " *"
1584+ throw (ArgumentError (" pyimport: wildcard import 'from $modname import *' is not supported" ))
1585+ end
1586+
1587+ parts = split (rest, " ," ; keepempty= false )
1588+ exprs = Expr[]
1589+ for part in parts
1590+ part = strip (part)
1591+ m2 = match (r" ^(\S +)\s +as\s +(\S +)$" , part)
1592+ name, alias = if ! isnothing (m2)
1593+ m2[1 ], Symbol (m2[2 ])
1594+ else
1595+ part, Symbol (part)
1596+ end
1597+
1598+ push! (exprs, :(const $ alias = pyimport ($ modname => $ name)))
1599+ end
1600+
1601+ length (exprs) == 1 ? exprs[1 ] : Expr (:block , exprs... )
1602+ end
1603+
14641604# ## builtins not covered elsewhere
14651605
14661606"""
0 commit comments