diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index c2baa8d15f26dedf0643ea5aa31db47abebfda9c..1a1c11e24606bc1e7b69bb642cb6dda7188ea5af 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -1735,14 +1735,20 @@ class BackquoteNode(ExprNode): code.put_gotref(self.py_result()) - class ImportNode(ExprNode): # Used as part of import statement implementation. # Implements result = - # __import__(module_name, globals(), None, name_list) + # __import__(module_name, globals(), None, name_list, level) # - # module_name StringNode dotted name of module + # module_name StringNode dotted name of module. Empty module + # name means importing the parent package accourding + # to level # name_list ListNode or None list of names to be imported + # level int relative import level: + # -1: attempt both relative import and absolute import; + # 0: absolute import; + # >0: the number of parent directories to search + # relative to the current module. type = py_object_type @@ -1765,10 +1771,11 @@ class ImportNode(ExprNode): else: name_list_code = "0" code.putln( - "%s = __Pyx_Import(%s, %s); %s" % ( + "%s = __Pyx_Import(%s, %s, %d); %s" % ( self.result(), self.module_name.py_result(), name_list_code, + self.level, code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) @@ -7659,10 +7666,10 @@ static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name) { import_utility_code = UtilityCode( proto = """ -static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list); /*proto*/ +static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, long level); /*proto*/ """, impl = """ -static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list) { +static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, long level) { PyObject *py_import = 0; PyObject *empty_list = 0; PyObject *module = 0; @@ -7686,8 +7693,23 @@ static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list) { empty_dict = PyDict_New(); if (!empty_dict) goto bad; + #if PY_VERSION_HEX >= 0x02050000 + { + PyObject *py_level = PyInt_FromLong(level); + if (!py_level) + goto bad; + module = PyObject_CallFunctionObjArgs(py_import, + name, global_dict, empty_dict, list, py_level, NULL); + Py_DECREF(py_level); + } + #else + if (level>0) { + PyErr_SetString(PyExc_RuntimeError, "Relative import is not supported for Python <=2.4."); + goto bad; + } module = PyObject_CallFunctionObjArgs(py_import, name, global_dict, empty_dict, list, NULL); + #endif bad: Py_XDECREF(empty_list); Py_XDECREF(py_import); diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index a2d38fe8c79eedb07157afbcb3e8e53d6d4b1716..d72aeaa11fc53acff7f6b79ef54492624b465c01 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -1189,6 +1189,8 @@ def p_raise_statement(s): return Nodes.ReraiseStatNode(pos) def p_import_statement(s): + # will do absolute import in Py3 and try both relative and absolute in Py2. + level = 0 if s.context.language_level >= 3 else -1 # s.sy in ('import', 'cimport') pos = s.position() kind = s.sy @@ -1216,6 +1218,7 @@ def p_import_statement(s): rhs = ExprNodes.ImportNode(pos, module_name = ExprNodes.IdentifierStringNode( pos, value = dotted_name), + level = level, name_list = name_list)) stats.append(stat) return Nodes.StatListNode(pos, stats = stats) @@ -1224,13 +1227,30 @@ def p_from_import_statement(s, first_statement = 0): # s.sy == 'from' pos = s.position() s.next() - (dotted_name_pos, _, dotted_name, _) = \ - p_dotted_name(s, as_allowed = 0) + if s.sy == '.': + # count relative import level + level = 0 + while s.sy == '.': + level += 1 + s.next() + else: + # will do absolute import in Py3 and try both relative and absolute in Py2. + level = 0 if s.context.language_level >= 3 else -1 + + if level > 0 and s.sy == 'cimport': + s.error("Relative cimport is not supported yet") + if level > 0 and s.sy == 'import': + # we are dealing with "from .. import foo, bar" + dotted_name_pos, dotted_name = s.position(), '' + else: + (dotted_name_pos, _, dotted_name, _) = \ + p_dotted_name(s, as_allowed = 0) if s.sy in ('import', 'cimport'): kind = s.sy s.next() else: s.error("Expected 'import' or 'cimport'") + is_cimport = kind == 'cimport' is_parenthesized = False if s.sy == '*': @@ -1252,6 +1272,8 @@ def p_from_import_statement(s, first_statement = 0): if dotted_name == '__future__': if not first_statement: s.error("from __future__ imports must occur at the beginning of the file") + elif level is not None: + s.error("invalid syntax") else: for (name_pos, name, as_name, kind) in imported_names: if name == "braces": @@ -1285,6 +1307,7 @@ def p_from_import_statement(s, first_statement = 0): return Nodes.FromImportStatNode(pos, module = ExprNodes.ImportNode(dotted_name_pos, module_name = ExprNodes.IdentifierStringNode(pos, value = dotted_name), + level = level, name_list = import_list), items = items) diff --git a/runtests.py b/runtests.py index 8b3ffa1d5118cea455c52aa181e067f690683baf..c68958f6eb707413d68f681df2ad5b2b98518fc7 100644 --- a/runtests.py +++ b/runtests.py @@ -93,6 +93,7 @@ VER_DEP_MODULES = { ]), (2,5) : (operator.lt, lambda x: x in ['run.any', 'run.all', + 'run.relativeimport_T542', ]), (2,6) : (operator.lt, lambda x: x in ['run.print_function', 'run.cython3', diff --git a/tests/compile/fromimport.pyx b/tests/compile/fromimport.pyx index 142999deede70d1603078f2ed97c005c8f07ec3e..46f7b54420534e79275ee355d31c05ef54864180 100644 --- a/tests/compile/fromimport.pyx +++ b/tests/compile/fromimport.pyx @@ -4,4 +4,12 @@ def f(): from spam import eggs from spam.morespam import bacon, eggs, ham from spam import eggs as ova - + from . import spam + from ... import spam + from .. import spam, foo + from ... import spam, foobar + from .spam import foo + from ...spam import foo, bar + from ...spam.foo import bar + from ...spam.foo import foo, bar + from ...spam.foo import (foo, bar) diff --git a/tests/compile/fromimport_star.pyx b/tests/compile/fromimport_star.pyx new file mode 100644 index 0000000000000000000000000000000000000000..b1daad980932e4d62b2391e92a53bd612c85bdec --- /dev/null +++ b/tests/compile/fromimport_star.pyx @@ -0,0 +1,4 @@ +from spam import * +from ...spam.foo import * +from . import * +from ... import * diff --git a/tests/run/importfrom.pyx b/tests/run/importfrom.pyx index 93d4e5eb0ac2a661828cfcc8d574a6e44a7a419d..52bc676113de36bc893af3fbd76202618d68f26a 100644 --- a/tests/run/importfrom.pyx +++ b/tests/run/importfrom.pyx @@ -61,16 +61,16 @@ def typed_imports(): cdef type t from sys import maxunicode - print maxunicode == sys.maxunicode + print(maxunicode == sys.maxunicode) from types import ModuleType as t - print t is types.ModuleType + print(t is types.ModuleType) try: from sys import version_info as maxunicode except TypeError, e: - print e + print(e) try: from sys import maxunicode as t except TypeError, e: - print e + print(e) diff --git a/tests/run/relativeimport_T542.pyx b/tests/run/relativeimport_T542.pyx new file mode 100644 index 0000000000000000000000000000000000000000..2994cc40f4061382ed554339bbf2dcc9fafdc9b7 --- /dev/null +++ b/tests/run/relativeimport_T542.pyx @@ -0,0 +1,29 @@ +# cython: language_level=3 +__name__='distutils.baregg' # fool Python we are in distutils +from distutils import cmd, core, version + +from .core import * +def test_relative(): + """ + >>> test_relative() == (cmd, core, 'distutils.version') + True + """ + from . import cmd, core + from . import (version, core) + from .version import __name__ + return cmd, core, __name__ + +def test_absolute(): + """ + >>> test_absolute() + Traceback (most recent call last): + ... + ImportError: No module named debug + """ + import debug + return + +__doc__ = """ +>>> setup == core.setup +True +""" diff --git a/tests/run/relativeimport_star_T542.pyx b/tests/run/relativeimport_star_T542.pyx new file mode 100644 index 0000000000000000000000000000000000000000..39ceeaab86d7af2032bb759df0bdba404476769c --- /dev/null +++ b/tests/run/relativeimport_star_T542.pyx @@ -0,0 +1,8 @@ +from distutils import core, version +__package__ = 'distutils.core' # fool Python we are in distutils +from . import * + +__doc__ = """ +>>> core.setup == setup +True +"""