Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-rope for openSUSE:Factory checked in at 2024-01-21 23:10:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-rope (Old) and /work/SRC/openSUSE:Factory/.python-rope.new.16006 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-rope" Sun Jan 21 23:10:11 2024 rev:33 rq:1140277 version:1.12.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-rope/python-rope.changes 2024-01-03 12:24:10.937710947 +0100 +++ /work/SRC/openSUSE:Factory/.python-rope.new.16006/python-rope.changes 2024-01-21 23:10:34.734366541 +0100 @@ -1,0 +2,13 @@ +Sun Jan 21 11:09:36 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 1.12.0: + * #733 skip directories with perm error when building + autoimport index (@MrBago) + * #722, #723 Remove site-packages from packages search tree + (@tkrabel) + * #738 Implement os.PathLike on Resource (@lieryan) + * #739, #736 Ensure autoimport requests uses indexes (@lieryan) + * #734, #735 raise exception when extracting the start of a + block without the end + +------------------------------------------------------------------- Old: ---- rope-1.11.0.tar.gz New: ---- rope-1.12.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-rope.spec ++++++ --- /var/tmp/diff_new_pack.rZPFPe/_old 2024-01-21 23:10:35.358389287 +0100 +++ /var/tmp/diff_new_pack.rZPFPe/_new 2024-01-21 23:10:35.358389287 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-rope # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-rope -Version: 1.11.0 +Version: 1.12.0 Release: 0 Summary: A python refactoring library License: LGPL-3.0-or-later ++++++ rope-1.11.0.tar.gz -> rope-1.12.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/CHANGELOG.md new/rope-1.12.0/CHANGELOG.md --- old/rope-1.11.0/CHANGELOG.md 2023-11-05 16:10:32.000000000 +0100 +++ new/rope-1.12.0/CHANGELOG.md 2024-01-18 05:30:13.000000000 +0100 @@ -1,5 +1,15 @@ # **Upcoming release** +- ... + +# Release 1.12.0 + +- #733 skip directories with perm error when building autoimport index (@MrBago) +- #722, #723 Remove site-packages from packages search tree (@tkrabel) +- #738 Implement os.PathLike on Resource (@lieryan) +- #739, #736 Ensure autoimport requests uses indexes (@lieryan) +- #734, #735 raise exception when extracting the start of a block without the end + # Release 1.11.0 - #710, #561 Implement `except*` syntax (@lieryan) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/PKG-INFO new/rope-1.12.0/PKG-INFO --- old/rope-1.11.0/PKG-INFO 2023-11-05 16:12:09.193045400 +0100 +++ new/rope-1.12.0/PKG-INFO 2024-01-18 05:43:22.713984300 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: rope -Version: 1.11.0 +Version: 1.12.0 Summary: a python refactoring library... Author-email: Ali Gholami Rudi <aligr...@users.sourceforge.net> Maintainer-email: Lie Ryan <lieryan...@proton.me> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/docs/contributing.rst new/rope-1.12.0/docs/contributing.rst --- old/rope-1.11.0/docs/contributing.rst 2023-11-05 16:08:39.000000000 +0100 +++ new/rope-1.12.0/docs/contributing.rst 2024-01-18 05:15:50.000000000 +0100 @@ -95,6 +95,13 @@ Rope uses GitHub_. The repository exists at `python-rope/rope`_. +Setting up for local development +================================ + +#. Clone repository: ``git clone https://github.com/python-rope/rope.git`` +#. Create a virtualenv: ``python -m venv rope-venv`` +#. Activate the virtualenv +#. Install the project into the venv: ``pip install -e '.[doc,dev]'`` Submitting pull requests ======================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/docs/release-process.rst new/rope-1.12.0/docs/release-process.rst --- old/rope-1.11.0/docs/release-process.rst 2023-10-15 08:08:40.000000000 +0200 +++ new/rope-1.12.0/docs/release-process.rst 2024-01-18 05:39:22.000000000 +0100 @@ -12,14 +12,15 @@ 1. Ensure tickets assigned to Milestones are up to date 2. Update ``CHANGELOG.md`` -3. Increment version number in ``pyproject.toml`` -4. `git commit && git push` -5. Tag the release with the tag annotation containing the release information, +3. Close milestone +4. Increment version number in ``pyproject.toml`` +5. `git commit && git push` +6. Tag the release with the tag annotation containing the release information, ``python bin/tag-release.py`` -6. ``python3 -m build`` -7. ``twine upload -s dist/rope-$VERSION.{tar.gz,whl}`` -8. Publish to Discussions Announcement -9. Close milestone +7. ``python3 -m build`` +8. ``twine upload dist/rope-$VERSION.{tar.gz,whl}`` +9. Publish to Discussions Announcement +10. Create Github Release Release Schedule diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/pyproject.toml new/rope-1.12.0/pyproject.toml --- old/rope-1.11.0/pyproject.toml 2023-11-05 16:09:16.000000000 +0100 +++ new/rope-1.12.0/pyproject.toml 2024-01-18 05:42:05.000000000 +0100 @@ -21,7 +21,7 @@ 'Programming Language :: Python :: 3.12', 'Topic :: Software Development', ] -version = '1.11.0' +version = '1.12.0' dependencies = ['pytoolconfig[global] >= 1.2.2'] [[project.authors]] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/rope/base/resources.py new/rope-1.12.0/rope/base/resources.py --- old/rope-1.11.0/rope/base/resources.py 2023-10-15 08:07:50.000000000 +0200 +++ new/rope-1.12.0/rope/base/resources.py 2024-01-18 05:15:50.000000000 +0100 @@ -34,7 +34,7 @@ from rope.base import change, exceptions, fscommands -class Resource: +class Resource(os.PathLike): """Represents files and folders in a project""" def __init__(self, project, path): @@ -49,6 +49,10 @@ hex(id(self)), ) + def __fspath__(self) -> str: + """Return the file system path of this resource""" + return self.project._get_resource_path(self.path) + def move(self, new_location): """Move resource to `new_location`""" self._perform_change( @@ -60,14 +64,17 @@ """Remove resource from the project""" self._perform_change(change.RemoveResource(self), "Removing <%s>" % self.path) + def is_dir(self): + """Alias for `is_folder()`""" + def is_folder(self): - """Return true if the resource is a folder""" + """Return True if the resource is a Folder""" def create(self): """Create this resource""" def exists(self): - return os.path.exists(self.real_path) + return os.path.exists(self) @property def parent(self): @@ -75,7 +82,7 @@ return self.project.get_folder(parent) @property - def path(self): + def path(self) -> str: """Return the path of this resource relative to the project root The path is the list of parent directories separated by '/' followed @@ -84,19 +91,18 @@ return self._path @property - def name(self): + def name(self) -> str: """Return the name of this resource""" return self.path.split("/")[-1] @property - def real_path(self): - """Return the file system path of this resource""" - return self.project._get_resource_path(self.path) + def real_path(self) -> str: + return os.fspath(self) @property - def pathlib(self): + def pathlib(self) -> Path: """Return the file as a pathlib path.""" - return Path(self.real_path) + return Path(self) def __eq__(self, obj): return self.__class__ == obj.__class__ and self.path == obj.path @@ -135,7 +141,7 @@ DeprecationWarning, stacklevel=2, ) - with open(self.real_path, "rb") as handle: + with open(self, "rb") as handle: return handle.read() return self.project.fscommands.read(self.real_path) @@ -165,7 +171,7 @@ def get_children(self): """Return the children of this folder""" try: - children = os.listdir(self.real_path) + children = os.listdir(self) except OSError: return [] result = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/rope/contrib/autoimport/models.py new/rope-1.12.0/rope/contrib/autoimport/models.py --- old/rope-1.11.0/rope/contrib/autoimport/models.py 2023-11-05 16:08:39.000000000 +0100 +++ new/rope-1.12.0/rope/contrib/autoimport/models.py 2024-01-18 05:15:50.000000000 +0100 @@ -9,6 +9,9 @@ def __repr__(self): return f'{self.__class__.__name__}("{self._query}")' + def explain(self): + return FinalQuery("EXPLAIN QUERY PLAN " + self._query) + class Query: def __init__(self, query: str, columns: List[str]): @@ -100,9 +103,14 @@ @classmethod def create_table(cls, connection): super().create_table(connection) - connection.execute("CREATE INDEX IF NOT EXISTS name ON names(name)") - connection.execute("CREATE INDEX IF NOT EXISTS module ON names(module)") - connection.execute("CREATE INDEX IF NOT EXISTS package ON names(package)") + # fmt: off + connection.execute("CREATE INDEX IF NOT EXISTS names_name ON names(name)") + connection.execute("CREATE INDEX IF NOT EXISTS names_module ON names(module)") + connection.execute("CREATE INDEX IF NOT EXISTS names_package ON names(package)") + connection.execute("CREATE INDEX IF NOT EXISTS names_name_nocase ON names(name COLLATE NOCASE)") + connection.execute("CREATE INDEX IF NOT EXISTS names_module_nocase ON names(module COLLATE NOCASE)") + connection.execute("CREATE INDEX IF NOT EXISTS names_package_nocase ON names(package COLLATE NOCASE)") + # fmt: on search_submodule_like = objects.where('module LIKE ("%." || ?)') search_module_like = objects.where("module LIKE (?)") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/rope/contrib/autoimport/sqlite.py new/rope-1.12.0/rope/contrib/autoimport/sqlite.py --- old/rope-1.11.0/rope/contrib/autoimport/sqlite.py 2023-11-05 16:08:39.000000000 +0100 +++ new/rope-1.12.0/rope/contrib/autoimport/sqlite.py 2024-01-18 05:15:50.000000000 +0100 @@ -113,7 +113,7 @@ autoimport = AutoImport(..., memory=True) """ self.project = project - project_package = get_package_tuple(Path(project.root.real_path), project) + project_package = get_package_tuple(project.root.pathlib, project) assert project_package is not None assert project_package.path is not None self.project_package = project_package @@ -175,9 +175,7 @@ f"file:rope-{project_hash}:?mode=memory&cache=shared", uri=True ) else: - return sqlite3.connect( - str(Path(project.ropefolder.real_path) / "autoimport.db") - ) + return sqlite3.connect(project.ropefolder.pathlib / "autoimport.db") @property def connection(self): @@ -542,17 +540,26 @@ return folder.is_dir() and folder.as_posix() != "/usr/bin" folders = self.project.get_python_path_folders() - folder_paths = map(lambda folder: Path(folder.real_path), folders) - folder_paths = filter(filter_folders, folder_paths) # type:ignore + folder_paths = filter(filter_folders, map(Path, folders)) return list(OrderedDict.fromkeys(folder_paths)) + def _safe_iterdir(self, folder: Path): + dirs = folder.iterdir() + while True: + try: + yield next(dirs) + except PermissionError: + pass + except StopIteration: + break + def _get_available_packages(self) -> List[Package]: packages: List[Package] = [ Package(module, Source.BUILTIN, None, PackageType.BUILTIN) for module in sys.builtin_module_names ] for folder in self._get_python_folders(): - for package in folder.iterdir(): + for package in self._safe_iterdir(folder): package_tuple = get_package_tuple(package, self.project) if package_tuple is None: continue @@ -602,7 +609,7 @@ if target_name in sys.builtin_module_names: return Package(target_name, Source.BUILTIN, None, PackageType.BUILTIN) for folder in self._get_python_folders(): - for package in folder.iterdir(): + for package in self._safe_iterdir(folder): package_tuple = get_package_tuple(package, self.project) if package_tuple is None: continue @@ -617,7 +624,7 @@ ) -> ModuleFile: assert self.project_package.path underlined = underlined if underlined else self.underlined - resource_path: Path = Path(resource.real_path) + resource_path: Path = resource.pathlib # The project doesn't need its name added to the path, # since the standard python file layout accounts for that # so we set add_package_name to False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/rope/contrib/autoimport/utils.py new/rope-1.12.0/rope/contrib/autoimport/utils.py --- old/rope-1.11.0/rope/contrib/autoimport/utils.py 2023-10-15 08:08:40.000000000 +0200 +++ new/rope-1.12.0/rope/contrib/autoimport/utils.py 2024-01-18 05:15:50.000000000 +0100 @@ -20,7 +20,7 @@ """ package_name = package_path.name package_type: PackageType - if package_name.startswith(".") or package_name == "__pycache__": + if package_name.startswith(".") or package_name in ["__pycache__", "site-packages"]: return None if package_name.endswith((".egg-info", ".dist-info")): return None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/rope/refactor/extract.py new/rope-1.12.0/rope/refactor/extract.py --- old/rope-1.11.0/rope/refactor/extract.py 2023-10-15 08:08:40.000000000 +0200 +++ new/rope-1.12.0/rope/refactor/extract.py 2024-01-18 05:15:50.000000000 +0100 @@ -444,8 +444,10 @@ def base_conditions(self, info): if info.region[1] > info.scope_region[1]: raise RefactoringError("Bad region selected for extract method") + end_line = info.region_lines[1] end_scope = info.global_scope.get_inner_scope_for_line(end_line) + if end_scope != info.scope and end_scope.get_end() != end_line: raise RefactoringError("Bad region selected for extract method") try: @@ -497,6 +499,14 @@ raise RefactoringError( "Extracted piece should contain complete statements." ) + unbalanced_region_finder = _UnbalancedRegionFinder( + info.region_lines[0], info.region_lines[1] + ) + unbalanced_region_finder.visit(info.pymodule.ast_node) + if unbalanced_region_finder.error: + raise RefactoringError( + "Extracted piece cannot contain the start of a block without the end." + ) def _is_region_on_a_word(self, info): if ( @@ -1093,6 +1103,34 @@ pass +class _UnbalancedRegionFinder(_BaseErrorFinder): + """ + Flag an error if we are including the start of a block without the end. + We detect this by ensuring there is no AST node that starts inside the + selected range but ends outside of it. + """ + + def __init__(self, line_start: int, line_end: int): + self.error = False + self.line_start = line_start + self.line_end = line_end + + def generic_visit(self, node: ast.AST): + if not hasattr(node, "end_lineno"): + super().generic_visit(node) # Visit children + return + ends_before_range_starts = node.end_lineno < self.line_start + starts_after_range_ends = node.lineno > self.line_end + if ends_before_range_starts or starts_after_range_ends: + return # Don't visit children + starts_on_or_after_range_start = node.lineno >= self.line_start + ends_after_range_ends = node.end_lineno > self.line_end + if starts_on_or_after_range_start and ends_after_range_ends: + self.error = True + return # Don't visit children + super().generic_visit(node) # Visit children + + class _GlobalFinder(ast.RopeNodeVisitor): def __init__(self): self.globals_ = OrderedSet() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/rope.egg-info/PKG-INFO new/rope-1.12.0/rope.egg-info/PKG-INFO --- old/rope-1.11.0/rope.egg-info/PKG-INFO 2023-11-05 16:12:09.000000000 +0100 +++ new/rope-1.12.0/rope.egg-info/PKG-INFO 2024-01-18 05:43:22.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: rope -Version: 1.11.0 +Version: 1.12.0 Summary: a python refactoring library... Author-email: Ali Gholami Rudi <aligr...@users.sourceforge.net> Maintainer-email: Lie Ryan <lieryan...@proton.me> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/ropetest/contrib/autoimport/autoimporttest.py new/rope-1.12.0/ropetest/contrib/autoimport/autoimporttest.py --- old/rope-1.11.0/ropetest/contrib/autoimport/autoimporttest.py 2023-11-05 16:08:39.000000000 +0100 +++ new/rope-1.12.0/ropetest/contrib/autoimport/autoimporttest.py 2024-01-18 05:15:50.000000000 +0100 @@ -186,3 +186,39 @@ with assert_database_is_preserved(conn), \ patch("rope.base.versioning.calculate_version_hash", return_value="up-to-date-value"): autoimport._setup_db() + + +class TestQueryUsesIndexes: + def explain(self, autoimport, query): + explanation = list(autoimport._execute(query.explain(), ("abc",)))[0][-1] + # the explanation text varies, on some sqlite version + explanation = explanation.replace("TABLE ", "") + return explanation + + def test_search_by_name_uses_index(self, autoimport): + query = models.Name.search_by_name.select_star() + assert ( + self.explain(autoimport, query) + == "SEARCH names USING INDEX names_name (name=?)" + ) + + def test_search_by_name_like_uses_index(self, autoimport): + query = models.Name.search_by_name_like.select_star() + assert ( + self.explain(autoimport, query) + == "SEARCH names USING INDEX names_name_nocase (name>? AND name<?)" + ) + + def test_search_module_like_uses_index(self, autoimport): + query = models.Name.search_module_like.select_star() + assert ( + self.explain(autoimport, query) + == "SEARCH names USING INDEX names_module_nocase (module>? AND module<?)" + ) + + def test_search_submodule_like_uses_index(self, autoimport): + query = models.Name.search_submodule_like.select_star() + assert ( + self.explain(autoimport, query) + == "SCAN names" # FIXME: avoid full table scan + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/ropetest/contrib/autoimporttest.py new/rope-1.12.0/ropetest/contrib/autoimporttest.py --- old/rope-1.11.0/ropetest/contrib/autoimporttest.py 2023-10-15 08:07:50.000000000 +0200 +++ new/rope-1.12.0/ropetest/contrib/autoimporttest.py 2024-01-18 05:15:50.000000000 +0100 @@ -134,13 +134,28 @@ self.assertIn(import_statement, self.importer.search("D")) def test_generate_full_cache(self): - """The single thread test takes much longer than the multithread test but is easier to debug""" + # The single thread test takes much longer than the multithread test but is easier to debug single_thread = False self.importer.generate_modules_cache(single_thread=single_thread) self.assertIn(("from typing import Dict", "Dict"), self.importer.search("Dict")) - self.assertTrue(len(self.importer._dump_all()) > 0) + self.assertGreater(len(self.importer._dump_all()), 0) for table in self.importer._dump_all(): - self.assertTrue(len(table) > 0) + self.assertGreater(len(table), 0) + + def test_skipping_directories_not_accessible_because_of_permission_error(self): + # The single thread test takes much longer than the multithread test but is easier to debug + single_thread = False + self.importer.generate_modules_cache(single_thread=single_thread) + + # Create a temporary directory and set permissions to 000 + import tempfile, sys + with tempfile.TemporaryDirectory() as dir: + import os + os.chmod(dir, 0o000) + self.importer.project.prefs.python_path = [dir] + self.importer.generate_modules_cache(single_thread=single_thread) + self.assertIn(("from typing import Dict", "Dict"), self.importer.search("Dict")) + self.assertGreater(len(self.importer._dump_all()), 0) class AutoImportObservingTest(unittest.TestCase): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/ropetest/projecttest.py new/rope-1.12.0/ropetest/projecttest.py --- old/rope-1.11.0/ropetest/projecttest.py 2023-10-15 08:07:50.000000000 +0200 +++ new/rope-1.12.0/ropetest/projecttest.py 2024-01-18 05:15:50.000000000 +0100 @@ -9,6 +9,7 @@ from rope.base.libutils import path_to_resource from rope.base.project import NoProject, Project, _realpath from rope.base.resourceobserver import FilteredResourceObserver +from rope.base.resources import File, Folder from ropetest import testutils @@ -610,6 +611,18 @@ self.assertEqual(2, len(source_folders)) self.assertTrue(self.project.root in source_folders and src in source_folders) + def test_folder_is_pathlike(self): + resource = self.project.root.create_folder("src") + self.assertIsInstance(resource, Folder) + + self.assertIsInstance(os.fspath(resource), str) + + def test_file_is_pathlike(self): + resource = self.project.root.create_file("mod.py") + self.assertIsInstance(resource, File) + + self.assertIsInstance(os.fspath(resource), str) + class ResourceObserverTest(unittest.TestCase): def setUp(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-1.11.0/ropetest/refactor/extracttest.py new/rope-1.12.0/ropetest/refactor/extracttest.py --- old/rope-1.11.0/ropetest/refactor/extracttest.py 2023-11-05 16:08:39.000000000 +0100 +++ new/rope-1.12.0/ropetest/refactor/extracttest.py 2024-01-18 05:15:50.000000000 +0100 @@ -1149,6 +1149,64 @@ end = code.rindex(")") + 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") + + def test_raising_exception_on_incomplete_block(self): + code = dedent("""\ + if True: + a = 1 + b = 2 + """) + start = code.index("if") + end = code.index("1") + 1 + with self.assertRaises(rope.base.exceptions.RefactoringError): + self.do_extract_method(code, start, end, "new_func") + + def test_raising_exception_on_incomplete_block_2(self): + code = dedent("""\ + if True: + a = 1 + # + b = 2 + """) + start = code.index("if") + end = code.index("1") + 1 + with self.assertRaises(rope.base.exceptions.RefactoringError): + self.do_extract_method(code, start, end, "new_func") + + def test_raising_exception_on_incomplete_block_3(self): + code = dedent("""\ + if True: + a = 1 + + b = 2 + """) + start = code.index("if") + end = code.index("1") + 1 + with self.assertRaises(rope.base.exceptions.RefactoringError): + self.do_extract_method(code, start, end, "new_func") + + def test_raising_exception_on_incomplete_block_4(self): + code = dedent("""\ + # + if True: + a = 1 + b = 2 + """) + start = code.index("#") + end = code.index("1") + 1 + with self.assertRaises(rope.base.exceptions.RefactoringError): + self.do_extract_method(code, start, end, "new_func") + + def test_raising_exception_on_incomplete_block_5(self): + code = dedent("""\ + if True: + if 0: + a = 1 + """) + start = code.index("if") + end = code.index("0:") + 2 + with self.assertRaises(rope.base.exceptions.RefactoringError): + self.do_extract_method(code, start, end, "new_func") def test_extract_method_and_extra_blank_lines(self): code = dedent("""\