diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml
index 64623e437767..cc01ea7d3952 100644
--- a/python/src/META-INF/python-core.xml
+++ b/python/src/META-INF/python-core.xml
@@ -745,6 +745,13 @@
+
+
+
+
+
diff --git a/python/src/com/jetbrains/python/refactoring/packages/PyConvertModuleToPackageAction.java b/python/src/com/jetbrains/python/refactoring/packages/PyConvertModuleToPackageAction.java
new file mode 100644
index 000000000000..0edc3389d1b0
--- /dev/null
+++ b/python/src/com/jetbrains/python/refactoring/packages/PyConvertModuleToPackageAction.java
@@ -0,0 +1,83 @@
+package com.jetbrains.python.refactoring.packages;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+
+import static com.jetbrains.python.psi.PyUtil.as;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyConvertModuleToPackageAction extends AnAction {
+ private static final Logger LOG = Logger.getInstance(PyConvertModuleToPackageAction.class);
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final PyFile pyFile = getPythonFile(e);
+ assert pyFile != null;
+ final Project project = e.getProject();
+ if (project != null) {
+ createPackageFromModule(pyFile, project);
+ }
+ }
+
+ @VisibleForTesting
+ public static void createPackageFromModule(@NotNull final PyFile file, @NotNull Project project) {
+ WriteCommandAction.runWriteCommandAction(project, new Runnable() {
+ public void run() {
+ final VirtualFile vFile = file.getVirtualFile();
+ final VirtualFile parentDir = vFile.getParent();
+ try {
+ final VirtualFile packageDir = parentDir.createChildDirectory(this, vFile.getNameWithoutExtension());
+ vFile.move(this, packageDir);
+ vFile.rename(this, PyNames.INIT_DOT_PY);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ });
+ }
+
+
+ @Override
+ public void update(AnActionEvent e) {
+ final boolean enabled = isEnabled(e);
+ e.getPresentation().setVisible(enabled);
+ e.getPresentation().setEnabled(enabled);
+ }
+
+ @Nullable
+ private static PyFile getPythonFile(@NotNull AnActionEvent e) {
+ final VirtualFile vFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
+ final Project project = e.getData(CommonDataKeys.PROJECT);
+ if (project != null && vFile != null && !vFile.isDirectory()) {
+ final PsiManager psiManager = PsiManager.getInstance(project);
+ return as(psiManager.findFile(vFile), PyFile.class);
+ }
+ return null;
+ }
+
+ private static boolean isEnabled(@NotNull AnActionEvent e) {
+ final Project project = e.getData(CommonDataKeys.PROJECT);
+ if (project == null) {
+ return false;
+ }
+ final PyFile file = getPythonFile(e);
+ return file != null && !PyUtil.isPackage(file);
+ }
+}
diff --git a/python/testData/refactoring/convertModuleToPackage/simple/after/a/__init__.py b/python/testData/refactoring/convertModuleToPackage/simple/after/a/__init__.py
new file mode 100644
index 000000000000..6be3d998b68a
--- /dev/null
+++ b/python/testData/refactoring/convertModuleToPackage/simple/after/a/__init__.py
@@ -0,0 +1,5 @@
+import b
+
+
+def func():
+ print(b)
diff --git a/python/testData/refactoring/convertModuleToPackage/simple/after/b.py b/python/testData/refactoring/convertModuleToPackage/simple/after/b.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/python/testData/refactoring/convertModuleToPackage/simple/before/a.py b/python/testData/refactoring/convertModuleToPackage/simple/before/a.py
new file mode 100644
index 000000000000..6be3d998b68a
--- /dev/null
+++ b/python/testData/refactoring/convertModuleToPackage/simple/before/a.py
@@ -0,0 +1,5 @@
+import b
+
+
+def func():
+ print(b)
diff --git a/python/testData/refactoring/convertModuleToPackage/simple/before/b.py b/python/testData/refactoring/convertModuleToPackage/simple/before/b.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyConvertModuleToPackageTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyConvertModuleToPackageTest.java
new file mode 100644
index 000000000000..68d3bdd2bc5d
--- /dev/null
+++ b/python/testSrc/com/jetbrains/python/refactoring/PyConvertModuleToPackageTest.java
@@ -0,0 +1,32 @@
+package com.jetbrains.python.refactoring;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.testFramework.PlatformTestUtil;
+import com.jetbrains.python.fixtures.PyTestCase;
+import com.jetbrains.python.psi.PyFile;
+
+import static com.jetbrains.python.refactoring.packages.PyConvertModuleToPackageAction.createPackageFromModule;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyConvertModuleToPackageTest extends PyTestCase {
+
+ // PY-4387
+ public void testSimple() throws Exception {
+ final String rootBeforePath = getTestName(true) + "/before";
+ final String rootAfterPath = getTestName(true) + "/after";
+ final VirtualFile copiedDirectory = myFixture.copyDirectoryToProject(rootBeforePath, "");
+ myFixture.configureByFile("a.py");
+
+ final PyFile moduleToConvert = assertInstanceOf(myFixture.getFile(), PyFile.class);
+ createPackageFromModule(moduleToConvert, myFixture.getProject());
+
+ PlatformTestUtil.assertDirectoriesEqual(copiedDirectory, getVirtualFileByName(getTestDataPath() +rootAfterPath));
+ }
+
+ @Override
+ protected String getTestDataPath() {
+ return super.getTestDataPath() + "/refactoring/convertModuleToPackage/";
+ }
+}