Skip to content

Commit f9645ce

Browse files
authored
Merge branch 'master' into fix-gh-796-api-docs
2 parents 99aac98 + 55169bb commit f9645ce

File tree

10 files changed

+201
-12
lines changed

10 files changed

+201
-12
lines changed

.all-contributorsrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,15 @@
564564
"contributions": [
565565
"code"
566566
]
567+
},
568+
{
569+
"login": "nicoolas25",
570+
"name": "Nicolas Zermati",
571+
"avatar_url": "https://avatars.githubusercontent.com/u/163953?v=4",
572+
"profile": "http://n.zermati.eu",
573+
"contributions": [
574+
"code"
575+
]
567576
}
568577
],
569578
"projectName": "rope",

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- #787 Add type hints to importinfo.py and add repr to ImportInfo (@lieryan)
44
- #786 Upgrade Actions used in Github Workflows (@lieryan)
55
- #785 Refactoring movetest.py (@lieryan)
6+
- #788 Introduce the `preferred_import_style` configuration (@nicoolas25, @lieryan)
67

78
# Release 1.13.0
89

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
8484
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MrBago"><img src="https://avatars.githubusercontent.com/u/223219?v=4?s=100" width="100px;" alt="Bago Amirbekian"/><br /><sub><b>Bago Amirbekian</b></sub></a><br /><a href="https://github.com/python-rope/rope/commits?author=MrBago" title="Code">💻</a></td>
8585
<td align="center" valign="top" width="14.28%"><a href="http://mender.ai"><img src="https://avatars.githubusercontent.com/u/3324?v=4?s=100" width="100px;" alt="Ray Myers"/><br /><sub><b>Ray Myers</b></sub></a><br /><a href="https://github.com/python-rope/rope/commits?author=raymyers" title="Code">💻</a></td>
8686
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sandratsy"><img src="https://avatars.githubusercontent.com/u/26302933?v=4?s=100" width="100px;" alt="Sandra Tan Shi Yun"/><br /><sub><b>Sandra Tan Shi Yun</b></sub></a><br /><a href="https://github.com/python-rope/rope/commits?author=sandratsy" title="Code">💻</a></td>
87+
<td align="center" valign="top" width="14.28%"><a href="http://n.zermati.eu"><img src="https://avatars.githubusercontent.com/u/163953?v=4?s=100" width="100px;" alt="Nicolas Zermati"/><br /><sub><b>Nicolas Zermati</b></sub></a><br /><a href="https://github.com/python-rope/rope/commits?author=nicoolas25" title="Code">💻</a></td>
8788
</tr>
8889
</tbody>
8990
</table>

docs/configuration.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ autoimport.* Options
6969

7070
.. autopytoolconfigtable:: rope.base.prefs.AutoimportPrefs
7171

72+
imports.* Options
73+
----------------
74+
75+
.. autopytoolconfigtable:: rope.base.prefs.ImportPrefs
7276

7377
Old Configuration File
7478
----------------------

docs/default_config.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,13 @@ def set_prefs(prefs):
107107
#
108108
# prefs["ignore_bad_imports"] = False
109109

110-
# If `True`, rope will insert new module imports as
111-
# `from <package> import <module>` by default.
110+
# Controls how rope inserts new import statements. Must be one of:
111+
#
112+
# - "normal-import" will insert `import <package>`
113+
# - "from-module" will insert `from <package> import <module>`
114+
# - "from-global" insert insert `from <package>.<module> import <object>`
112115
#
113-
# prefs["prefer_module_from_imports"] = False
116+
# prefs.imports.preferred_import_style = "normal-import"
114117

115118
# If `True`, rope will transform a comma list of imports into
116119
# multiple separate import statements when organizing

docs/library.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,14 +522,16 @@ exception.
522522
Refactorings
523523
============
524524

525-
Have a look at the `rope.refactor` package and its sub-modules. For
525+
Have a look at the ``rope.refactor`` package and its sub-modules. For
526526
example for performing a move refactoring you can create an object
527527
representing this operation (which will be an instance of e.g.
528528
`MoveMethod`, `MoveModule`, ...) using `create_move`.
529529

530530
.. code-block:: python
531531
532-
mover = Move(project, resource, offset)
532+
from rope.refactor.move import create_move
533+
534+
mover = create_move(project, resource, offset)
533535
534536
Where ``resource`` and ``offset`` is the location to perform the
535537
refactoring.

rope/base/prefs.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
# mypy reports many problems.
2-
# type: ignore
31
"""Rope preferences."""
2+
from enum import Enum
43
from dataclasses import asdict, dataclass
54
from textwrap import dedent
65
from typing import Any, Callable, Dict, List, Optional, Tuple
@@ -36,6 +35,20 @@ class AutoimportPrefs:
3635
)
3736

3837

38+
@dataclass
39+
class ImportPrefs:
40+
preferred_import_style: str = field(
41+
default="default",
42+
description=dedent("""
43+
Controls how rope inserts new import statements. If set to
44+
``"normal-import"`` (default) will insert ``import <package>``; if
45+
set to ``"from-module"`` will insert ``from <package> import
46+
<module>``; if set to ``"from-global"`` rope will insert ``from
47+
<package>.<module> import <object>``.
48+
"""),
49+
)
50+
51+
3952
@dataclass
4053
class Prefs:
4154
"""Class to store rope preferences."""
@@ -151,7 +164,7 @@ class Prefs:
151164
default=False,
152165
description=dedent("""
153166
If ``True`` modules with syntax errors are considered to be empty.
154-
The default value is ``False``; When ``False`` syntax errors raise
167+
The default value is ``False``; when ``False`` syntax errors raise
155168
``rope.base.exceptions.ModuleSyntaxError`` exception.
156169
"""),
157170
)
@@ -166,8 +179,8 @@ class Prefs:
166179
prefer_module_from_imports: bool = field(
167180
default=False,
168181
description=dedent("""
169-
If ``True``, rope will insert new module imports as ``from
170-
<package> import <module>`` by default.
182+
**Deprecated**. ``imports.preferred_import_style`` takes
183+
precedence over ``prefer_module_from_imports``.
171184
"""),
172185
)
173186

@@ -234,7 +247,13 @@ class Prefs:
234247
"""),
235248
)
236249
autoimport: AutoimportPrefs = field(
237-
default_factory=AutoimportPrefs, description="Preferences for Autoimport")
250+
default_factory=AutoimportPrefs,
251+
description="Preferences for Autoimport",
252+
)
253+
imports: ImportPrefs = field(
254+
default_factory=ImportPrefs,
255+
description="Preferences for Import Organiser",
256+
)
238257

239258
def set(self, key: str, value: Any):
240259
"""Set the value of `key` preference to `value`."""
@@ -322,3 +341,21 @@ def get_config(root: Folder, ropefolder: Folder) -> PyToolConfig:
322341
global_config=True,
323342
)
324343
return config
344+
345+
346+
class ImportStyle(Enum): # FIXME: Use StrEnum once we're on minimum Python 3.11
347+
normal_import = "normal-import"
348+
from_module = "from-module"
349+
from_global = "from-global"
350+
351+
352+
DEFAULT_IMPORT_STYLE = ImportStyle.normal_import
353+
354+
355+
def get_preferred_import_style(prefs: Prefs) -> ImportStyle:
356+
try:
357+
return ImportStyle(prefs.imports.preferred_import_style)
358+
except ValueError:
359+
if prefs.imports.preferred_import_style == "default" and prefs.prefer_module_from_imports:
360+
return ImportStyle.from_module
361+
return DEFAULT_IMPORT_STYLE

rope/refactor/importutils/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import rope.base.codeanalyze
99
import rope.base.evaluate
1010
from rope.base import libutils
11+
from rope.base.prefs import get_preferred_import_style
12+
from rope.base.prefs import ImportStyle
1113
from rope.base.change import ChangeContents, ChangeSet
1214
from rope.refactor import occurrences, rename
1315
from rope.refactor.importutils import actions, module_imports
@@ -299,20 +301,23 @@ def get_module_imports(project, pymodule):
299301

300302

301303
def add_import(project, pymodule, module_name, name=None):
304+
preferred_import_style = get_preferred_import_style(project.prefs)
302305
imports = get_module_imports(project, pymodule)
303306
candidates = []
304307
names = []
305308
selected_import = None
306309
# from mod import name
307310
if name is not None:
308311
from_import = FromImport(module_name, 0, [(name, None)])
312+
if preferred_import_style == ImportStyle.from_global:
313+
selected_import = from_import
309314
names.append(name)
310315
candidates.append(from_import)
311316
# from pkg import mod
312317
if "." in module_name:
313318
pkg, mod = module_name.rsplit(".", 1)
314319
from_import = FromImport(pkg, 0, [(mod, None)])
315-
if project.prefs.get("prefer_module_from_imports"):
320+
if preferred_import_style == ImportStyle.from_module:
316321
selected_import = from_import
317322
candidates.append(from_import)
318323
if name:

ropetest/refactor/importutilstest.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,68 @@
11
import unittest
22
from textwrap import dedent
33

4+
from rope.base.prefs import get_preferred_import_style, ImportStyle, Prefs, ImportPrefs
5+
from rope.base.prefs import DEFAULT_IMPORT_STYLE
46
from rope.refactor.importutils import ImportTools, add_import, importinfo
57
from ropetest import testutils
68

79

10+
class TestImportPrefs:
11+
def test_preferred_import_style_is_normal_import(self, project):
12+
pref = Prefs(imports=ImportPrefs(preferred_import_style="normal-import"))
13+
assert pref.imports.preferred_import_style == "normal-import"
14+
assert get_preferred_import_style(pref) == ImportStyle.normal_import
15+
16+
def test_preferred_import_style_is_from_module(self, project):
17+
pref = Prefs(imports=ImportPrefs(preferred_import_style="from-module"))
18+
assert pref.imports.preferred_import_style == "from-module"
19+
assert get_preferred_import_style(pref) == ImportStyle.from_module
20+
21+
def test_preferred_import_style_is_from_global(self, project):
22+
pref = Prefs(imports=ImportPrefs(preferred_import_style="from-global"))
23+
assert pref.imports.preferred_import_style == "from-global"
24+
assert get_preferred_import_style(pref) == ImportStyle.from_global
25+
26+
def test_invalid_preferred_import_style_is_default(self, project):
27+
pref = Prefs(imports=ImportPrefs(preferred_import_style="invalid-value"))
28+
assert pref.imports.preferred_import_style == "invalid-value"
29+
assert get_preferred_import_style(pref) == DEFAULT_IMPORT_STYLE
30+
assert get_preferred_import_style(pref) == ImportStyle.normal_import
31+
32+
def test_default_preferred_import_style_default_is_normal_imports(self, project):
33+
pref = Prefs()
34+
assert pref.imports.preferred_import_style == "default"
35+
assert get_preferred_import_style(pref) == ImportStyle.normal_import
36+
37+
def test_default_preferred_import_style_default_and_prefer_module_from_imports(self, project):
38+
pref = Prefs(
39+
prefer_module_from_imports=True,
40+
imports=ImportPrefs(preferred_import_style="default"),
41+
)
42+
assert get_preferred_import_style(pref) == ImportStyle.from_module
43+
44+
def test_preferred_import_style_is_normal_import_takes_precedence_over_prefer_module_from_imports(self, project):
45+
pref = Prefs(
46+
prefer_module_from_imports=True,
47+
imports=ImportPrefs(preferred_import_style="normal_import"),
48+
)
49+
assert get_preferred_import_style(pref) == ImportStyle.normal_import
50+
51+
def test_preferred_import_style_is_from_module_takes_precedence_over_prefer_module_from_imports(self, project):
52+
pref = Prefs(
53+
prefer_module_from_imports=True,
54+
imports=ImportPrefs(preferred_import_style="from-module"),
55+
)
56+
assert get_preferred_import_style(pref) == ImportStyle.from_module
57+
58+
def test_preferred_import_style_is_from_global_takes_precedence_over_prefer_module_from_imports(self, project):
59+
pref = Prefs(
60+
prefer_module_from_imports=True,
61+
imports=ImportPrefs(preferred_import_style="from-global"),
62+
)
63+
assert get_preferred_import_style(pref) == ImportStyle.from_global
64+
65+
866
class ImportUtilsTest(unittest.TestCase):
967
def setUp(self):
1068
super().setUp()

ropetest/refactor/movetest.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,75 @@ def a_function():
254254
self.mod3.read(),
255255
)
256256

257+
def test_adding_imports_preferred_import_style_is_normal_import(self) -> None:
258+
self.project.prefs.imports.preferred_import_style = "normal-import"
259+
self.origin_module.write(dedent("""\
260+
class AClass(object):
261+
pass
262+
def a_function():
263+
pass
264+
"""))
265+
self.mod3.write(dedent("""\
266+
import origin_module
267+
a_var = origin_module.AClass()
268+
origin_module.a_function()"""))
269+
# Move to destination_module_in_pkg which is in a different package
270+
self._move(self.origin_module, self.origin_module.read().index("AClass") + 1, self.destination_module_in_pkg)
271+
self.assertEqual(
272+
dedent("""\
273+
import origin_module
274+
import pkg.destination_module_in_pkg
275+
a_var = pkg.destination_module_in_pkg.AClass()
276+
origin_module.a_function()"""),
277+
self.mod3.read(),
278+
)
279+
280+
def test_adding_imports_preferred_import_style_is_from_module(self) -> None:
281+
self.project.prefs.imports.preferred_import_style = "from-module"
282+
self.origin_module.write(dedent("""\
283+
class AClass(object):
284+
pass
285+
def a_function():
286+
pass
287+
"""))
288+
self.mod3.write(dedent("""\
289+
import origin_module
290+
a_var = origin_module.AClass()
291+
origin_module.a_function()"""))
292+
# Move to destination_module_in_pkg which is in a different package
293+
self._move(self.origin_module, self.origin_module.read().index("AClass") + 1, self.destination_module_in_pkg)
294+
self.assertEqual(
295+
dedent("""\
296+
import origin_module
297+
from pkg import destination_module_in_pkg
298+
a_var = destination_module_in_pkg.AClass()
299+
origin_module.a_function()"""),
300+
self.mod3.read(),
301+
)
302+
303+
def test_adding_imports_preferred_import_style_is_from_global(self) -> None:
304+
self.project.prefs.imports.preferred_import_style = "from-global"
305+
self.origin_module.write(dedent("""\
306+
class AClass(object):
307+
pass
308+
def a_function():
309+
pass
310+
"""))
311+
self.mod3.write(dedent("""\
312+
import origin_module
313+
a_var = origin_module.AClass()
314+
origin_module.a_function()"""))
315+
# Move to destination_module_in_pkg which is in a different package
316+
self._move(self.origin_module, self.origin_module.read().index("AClass") + 1, self.destination_module_in_pkg)
317+
self.assertEqual(
318+
dedent("""\
319+
import origin_module
320+
from pkg.destination_module_in_pkg import AClass
321+
a_var = AClass()
322+
origin_module.a_function()"""),
323+
self.mod3.read(),
324+
)
325+
257326
def test_adding_imports_noprefer_from_module(self) -> None:
258327
self.project.prefs["prefer_module_from_imports"] = False
259328
self.origin_module.write(dedent("""\

0 commit comments

Comments
 (0)