← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:py3only-scandir into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:py3only-scandir into launchpad:master.

Commit message:
Use os.scandir and os.walk directly

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/407246

The separate `scandir` module was a backport of the same code to Python versions earlier than 3.5, and is no longer needed.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:py3only-scandir into launchpad:master.
diff --git a/configs/development/gunicorn.conf.py b/configs/development/gunicorn.conf.py
index 0576e0d..2c914aa 100644
--- a/configs/development/gunicorn.conf.py
+++ b/configs/development/gunicorn.conf.py
@@ -1,8 +1,6 @@
 from fnmatch import fnmatch
 import os
 
-import scandir
-
 
 BASE_DIR = os.path.realpath(
     os.path.join(os.path.dirname(__file__), '..', '..'))
@@ -13,7 +11,7 @@ def find_files(directory, pattern):
     """Find files in `directory` matching `pattern`.
     """
     result = []
-    for root, dirs, files in scandir.walk(directory):
+    for root, dirs, files in os.walk(directory):
         for basename in files:
             matches = fnmatch(basename, pattern)
             if matches:
diff --git a/lib/lp/archivepublisher/customupload.py b/lib/lp/archivepublisher/customupload.py
index 741211a..42dfea3 100644
--- a/lib/lp/archivepublisher/customupload.py
+++ b/lib/lp/archivepublisher/customupload.py
@@ -20,7 +20,6 @@ import shutil
 import tarfile
 import tempfile
 
-import scandir
 from zope.interface import implementer
 
 from lp.archivepublisher.debversion import (
@@ -290,7 +289,7 @@ class CustomUpload:
         """Install the files from the custom upload to the archive."""
         assert self.tmpdir is not None, "Must extract tarfile first"
         extracted = False
-        for dirpath, dirnames, filenames in scandir.walk(self.tmpdir):
+        for dirpath, dirnames, filenames in os.walk(self.tmpdir):
 
             # Create symbolic links to directories.
             for dirname in dirnames:
@@ -359,7 +358,7 @@ class CustomUpload:
         # now present in the target. Deliberately skip 'broken' versions
         # because they can't be sorted anyway.
         versions = []
-        for entry in scandir.scandir(self.targetdir):
+        for entry in os.scandir(self.targetdir):
             # Skip the symlink.
             if entry.name == 'current':
                 continue
diff --git a/lib/lp/archivepublisher/model/ftparchive.py b/lib/lp/archivepublisher/model/ftparchive.py
index 624aebf..d56b881 100644
--- a/lib/lp/archivepublisher/model/ftparchive.py
+++ b/lib/lp/archivepublisher/model/ftparchive.py
@@ -6,7 +6,6 @@ import os
 import re
 import time
 
-import scandir
 import six
 from storm.expr import (
     Desc,
@@ -61,7 +60,7 @@ def make_clean_dir(path, clean_pattern=".*"):
         If omitted, all files are removed.
     """
     if os.path.isdir(path):
-        for entry in list(scandir.scandir(path)):
+        for entry in list(os.scandir(path)):
             if (entry.name == "by-hash" or
                     not re.match(clean_pattern, entry.name)):
                 # Ignore existing by-hash directories; they will be cleaned
diff --git a/lib/lp/archivepublisher/publishing.py b/lib/lp/archivepublisher/publishing.py
index 0975176..daea42c 100644
--- a/lib/lp/archivepublisher/publishing.py
+++ b/lib/lp/archivepublisher/publishing.py
@@ -36,7 +36,6 @@ from debian.deb822 import (
     _multivalued,
     Release,
     )
-import scandir
 import six
 from storm.expr import Desc
 from zope.component import getUtility
@@ -358,7 +357,7 @@ class ByHash:
             hash_path = os.path.join(self.path, archive_hash.apt_name)
             if os.path.exists(hash_path):
                 prune_hash_directory = True
-                for entry in list(scandir.scandir(hash_path)):
+                for entry in list(os.scandir(hash_path)):
                     if entry.name not in self.known_digests[
                             archive_hash.apt_name]:
                         self.log.debug(
@@ -1189,7 +1188,7 @@ class Publisher(object):
                 distroseries, pocket, component, core_files)
             dep11_dir = os.path.join(suite_dir, component, "dep11")
             try:
-                for entry in scandir.scandir(dep11_dir):
+                for entry in os.scandir(dep11_dir):
                     if (entry.name.startswith("Components-") or
                             entry.name.startswith("icons-")):
                         dep11_path = os.path.join(
@@ -1355,7 +1354,7 @@ class Publisher(object):
         i18n_dir = os.path.join(self._config.distsroot, suite, i18n_subpath)
         i18n_files = set()
         try:
-            for entry in scandir.scandir(i18n_dir):
+            for entry in os.scandir(i18n_dir):
                 if not entry.name.startswith('Translation-'):
                     continue
                 i18n_files.add(remove_suffix(entry.name))
@@ -1556,7 +1555,7 @@ class DirectoryHash:
 
     def add_dir(self, path):
         """Recursively add a directory path to be checksummed."""
-        for dirpath, dirnames, filenames in scandir.walk(path):
+        for dirpath, dirnames, filenames in os.walk(path):
             for filename in filenames:
                 self.add(os.path.join(dirpath, filename))
 
diff --git a/lib/lp/archivepublisher/scripts/publish_ftpmaster.py b/lib/lp/archivepublisher/scripts/publish_ftpmaster.py
index 5326141..25c7a21 100644
--- a/lib/lp/archivepublisher/scripts/publish_ftpmaster.py
+++ b/lib/lp/archivepublisher/scripts/publish_ftpmaster.py
@@ -14,7 +14,6 @@ import os
 import shutil
 
 from pytz import utc
-import scandir
 import six
 from zope.component import getUtility
 
@@ -471,7 +470,7 @@ class PublishFTPMaster(LaunchpadCronScript):
         backup_top = os.path.join(get_backup_dists(archive_config), suite)
         staging_top = os.path.join(archive_config.stagingroot, suite)
         updated = False
-        for staging_dir, _, filenames in scandir.walk(staging_top):
+        for staging_dir, _, filenames in os.walk(staging_top):
             rel_dir = os.path.relpath(staging_dir, staging_top)
             backup_dir = os.path.join(backup_top, rel_dir)
             for filename in filenames:
diff --git a/lib/lp/archivepublisher/signing.py b/lib/lp/archivepublisher/signing.py
index ec61b95..2a2f08d 100644
--- a/lib/lp/archivepublisher/signing.py
+++ b/lib/lp/archivepublisher/signing.py
@@ -27,7 +27,6 @@ import tempfile
 import textwrap
 
 from pytz import utc
-import scandir
 from zope.component import getUtility
 
 from lp.archivepublisher.config import getPubConfig
@@ -282,7 +281,7 @@ class SigningUpload(CustomUpload):
             SigningKeyType.FIT: self.signFit,
             }
 
-        for dirpath, dirnames, filenames in scandir.walk(self.tmpdir):
+        for dirpath, dirnames, filenames in os.walk(self.tmpdir):
             for filename in filenames:
                 file_path = os.path.join(dirpath, filename)
                 if filename.endswith(".efi"):
diff --git a/lib/lp/archivepublisher/tests/test_publisher.py b/lib/lp/archivepublisher/tests/test_publisher.py
index f2c62cf..9000e9d 100644
--- a/lib/lp/archivepublisher/tests/test_publisher.py
+++ b/lib/lp/archivepublisher/tests/test_publisher.py
@@ -32,7 +32,6 @@ from unittest import mock
 from debian.deb822 import Release
 from fixtures import MonkeyPatch
 import pytz
-import scandir
 import six
 from testscenarios import (
     load_tests_apply_scenarios,
@@ -512,7 +511,7 @@ class ByHashesHaveContents(Matcher):
 
     def match(self, root):
         children = set()
-        for dirpath, dirnames, _ in scandir.walk(root):
+        for dirpath, dirnames, _ in os.walk(root):
             if "by-hash" in dirnames:
                 children.add(os.path.relpath(dirpath, root))
         mismatch = MatchesSetwise(
diff --git a/lib/lp/archivepublisher/tests/test_signing.py b/lib/lp/archivepublisher/tests/test_signing.py
index 95e314a..20ba90e 100644
--- a/lib/lp/archivepublisher/tests/test_signing.py
+++ b/lib/lp/archivepublisher/tests/test_signing.py
@@ -18,7 +18,6 @@ from fixtures import (
     MonkeyPatch,
     )
 from pytz import utc
-import scandir
 import six
 from testtools.matchers import (
     Contains,
@@ -79,7 +78,7 @@ class SignedMatches(Matcher):
 
     def match(self, base):
         content = []
-        for root, dirs, files in scandir.walk(base):
+        for root, dirs, files in os.walk(base):
             content.extend(
                 [os.path.relpath(os.path.join(root, f), base) for f in files])
 
diff --git a/lib/lp/archiveuploader/charmrecipeupload.py b/lib/lp/archiveuploader/charmrecipeupload.py
index d31e8f1..e4da3e7 100644
--- a/lib/lp/archiveuploader/charmrecipeupload.py
+++ b/lib/lp/archiveuploader/charmrecipeupload.py
@@ -10,7 +10,6 @@ __all__ = [
 
 import os
 
-import scandir
 from zope.component import getUtility
 
 from lp.archiveuploader.utils import UploadError
@@ -39,7 +38,7 @@ class CharmRecipeUpload:
 
         found_charm = False
         charm_paths = []
-        for dirpath, _, filenames in scandir.walk(self.upload_path):
+        for dirpath, _, filenames in os.walk(self.upload_path):
             if dirpath == self.upload_path:
                 # All relevant files will be in a subdirectory.
                 continue
diff --git a/lib/lp/archiveuploader/dscfile.py b/lib/lp/archiveuploader/dscfile.py
index ba47bd3..7efbc85 100644
--- a/lib/lp/archiveuploader/dscfile.py
+++ b/lib/lp/archiveuploader/dscfile.py
@@ -30,7 +30,6 @@ from debian.deb822 import (
     Deb822Dict,
     PkgRelation,
     )
-import scandir
 import six
 from zope.component import getUtility
 
@@ -617,7 +616,7 @@ class DSCFile(SourceUploadFile, SignableTagFile):
 
             # Check if 'dpkg-source' created only one directory.
             temp_directories = [
-                entry.name for entry in scandir.scandir(unpacked_dir)
+                entry.name for entry in os.scandir(unpacked_dir)
                 if entry.is_dir()]
             if len(temp_directories) > 1:
                 yield UploadError(
diff --git a/lib/lp/archiveuploader/livefsupload.py b/lib/lp/archiveuploader/livefsupload.py
index 4034db4..7eedbe3 100644
--- a/lib/lp/archiveuploader/livefsupload.py
+++ b/lib/lp/archiveuploader/livefsupload.py
@@ -7,7 +7,6 @@ __metaclass__ = type
 
 import os
 
-import scandir
 from zope.component import getUtility
 
 from lp.buildmaster.enums import BuildStatus
@@ -37,7 +36,7 @@ class LiveFSUpload:
         """Process this upload, loading it into the database."""
         self.logger.debug("Beginning processing.")
 
-        for dirpath, _, filenames in scandir.walk(self.upload_path):
+        for dirpath, _, filenames in os.walk(self.upload_path):
             if dirpath == self.upload_path:
                 # All relevant files will be in a subdirectory.
                 continue
diff --git a/lib/lp/archiveuploader/ocirecipeupload.py b/lib/lp/archiveuploader/ocirecipeupload.py
index cd99962..516ef90 100644
--- a/lib/lp/archiveuploader/ocirecipeupload.py
+++ b/lib/lp/archiveuploader/ocirecipeupload.py
@@ -10,7 +10,6 @@ __all__ = ['OCIRecipeUpload']
 import json
 import os
 
-import scandir
 from zope.component import getUtility
 
 from lp.archiveuploader.utils import UploadError
@@ -39,7 +38,7 @@ class OCIRecipeUpload:
         self.logger.debug("Beginning processing.")
 
         # Find digest file
-        for dirpath, _, filenames in scandir.walk(self.upload_path):
+        for dirpath, _, filenames in os.walk(self.upload_path):
             if dirpath == self.upload_path:
                 # All relevant files will be in a subdirectory.
                 continue
diff --git a/lib/lp/archiveuploader/snapupload.py b/lib/lp/archiveuploader/snapupload.py
index e8b7bff..a2b4059 100644
--- a/lib/lp/archiveuploader/snapupload.py
+++ b/lib/lp/archiveuploader/snapupload.py
@@ -7,7 +7,6 @@ __metaclass__ = type
 
 import os
 
-import scandir
 from zope.component import getUtility
 
 from lp.archiveuploader.utils import UploadError
@@ -40,7 +39,7 @@ class SnapUpload:
 
         found_snap = False
         snap_paths = []
-        for dirpath, _, filenames in scandir.walk(self.upload_path):
+        for dirpath, _, filenames in os.walk(self.upload_path):
             if dirpath == self.upload_path:
                 # All relevant files will be in a subdirectory.
                 continue
diff --git a/lib/lp/archiveuploader/uploadprocessor.py b/lib/lp/archiveuploader/uploadprocessor.py
index 54510c7..0c8911b 100644
--- a/lib/lp/archiveuploader/uploadprocessor.py
+++ b/lib/lp/archiveuploader/uploadprocessor.py
@@ -51,7 +51,6 @@ import os
 import shutil
 import sys
 
-import scandir
 from sqlobject import SQLObjectNotFound
 from zope.component import getUtility
 
@@ -215,7 +214,7 @@ class UploadProcessor:
             alphabetically sorted.
         """
         return sorted(
-            entry.name for entry in scandir.scandir(fsroot) if entry.is_dir())
+            entry.name for entry in os.scandir(fsroot) if entry.is_dir())
 
 
 class UploadHandler:
@@ -262,7 +261,7 @@ class UploadHandler:
         """
         changes_files = []
 
-        for dirpath, dirnames, filenames in scandir.walk(self.upload_path):
+        for dirpath, dirnames, filenames in os.walk(self.upload_path):
             relative_path = dirpath[len(self.upload_path) + 1:]
             for filename in filenames:
                 if filename.endswith(".changes"):
diff --git a/lib/lp/bugs/tests/test_doc.py b/lib/lp/bugs/tests/test_doc.py
index 627025b..78f8cbf 100644
--- a/lib/lp/bugs/tests/test_doc.py
+++ b/lib/lp/bugs/tests/test_doc.py
@@ -9,8 +9,6 @@ import logging
 import os
 import unittest
 
-import scandir
-
 from lp.code.tests.test_doc import branchscannerSetUp
 from lp.services.config import config
 from lp.services.features.testing import FeatureFixture
@@ -484,7 +482,7 @@ def test_suite():
     stories_dir = os.path.join(os.path.pardir, 'stories')
     suite.addTest(PageTestSuite(stories_dir))
     stories_path = os.path.join(here, stories_dir)
-    for story_entry in scandir.scandir(stories_path):
+    for story_entry in os.scandir(stories_path):
         if not story_entry.is_dir():
             continue
         story_path = os.path.join(stories_dir, story_entry.name)
diff --git a/lib/lp/registry/model/codeofconduct.py b/lib/lp/registry/model/codeofconduct.py
index 7fae872..f4eca01 100644
--- a/lib/lp/registry/model/codeofconduct.py
+++ b/lib/lp/registry/model/codeofconduct.py
@@ -14,7 +14,6 @@ from datetime import datetime
 import os
 
 import pytz
-import scandir
 import six
 from storm.locals import (
     Bool,
@@ -154,7 +153,7 @@ class CodeOfConductSet:
         cocs_path = getUtility(ICodeOfConductConf).path
 
         # iter through files and store the CoC Object
-        for entry in scandir.scandir(cocs_path):
+        for entry in os.scandir(cocs_path):
             # Select the correct filenames
             if entry.name.endswith('.txt'):
                 # Extract the version from filename
diff --git a/lib/lp/registry/scripts/productreleasefinder/walker.py b/lib/lp/registry/scripts/productreleasefinder/walker.py
index 6dff7de..9dfbbf0 100644
--- a/lib/lp/registry/scripts/productreleasefinder/walker.py
+++ b/lib/lp/registry/scripts/productreleasefinder/walker.py
@@ -13,6 +13,7 @@ __all__ = [
     ]
 
 import ftplib
+import os
 import socket
 
 from lazr.uri import (
@@ -20,7 +21,6 @@ from lazr.uri import (
     URI,
     )
 import requests
-import scandir
 from six.moves.urllib.parse import (
     unquote_plus,
     urljoin,
@@ -55,7 +55,7 @@ class WalkerBase:
     """Base class for URL walkers.
 
     This class is a base class for those wishing to implement protocol
-    specific walkers.  Walkers behave much like the scandir.walk() function,
+    specific walkers.  Walkers behave much like the os.walk() function,
     but taking a URL and working remotely.
 
     A typical usage would be:
@@ -108,7 +108,7 @@ class WalkerBase:
         """Walk through the URL.
 
         Yields (dirpath, dirnames, filenames) for each path under the base;
-        dirnames can be modified as with scandir.walk.
+        dirnames can be modified as with os.walk.
         """
         try:
             self.open()
@@ -384,7 +384,7 @@ def walk(url, log_parent=None):
     elif scheme in ["http", "https"]:
         return HTTPWalker(url, log_parent)
     elif scheme in ["file"]:
-        return scandir.walk(path)
+        return os.walk(path)
     else:
         raise WalkerError("Unknown scheme: %s" % scheme)
 
diff --git a/lib/lp/scripts/utilities/js/jsbuild.py b/lib/lp/scripts/utilities/js/jsbuild.py
index 5b7bde8..77ce702 100644
--- a/lib/lp/scripts/utilities/js/jsbuild.py
+++ b/lib/lp/scripts/utilities/js/jsbuild.py
@@ -16,7 +16,6 @@ import sys
 
 import cssutils
 from cssutils import settings
-import scandir
 
 
 HERE = os.path.dirname(__file__)
@@ -256,7 +255,7 @@ class Builder:
             return
 
         # Process sub-skins.
-        for entry in scandir.scandir(src_skins_dir):
+        for entry in os.scandir(src_skins_dir):
             self.build_skin(component_name, entry.name)
 
     def link_directory_content(self, src_dir, target_dir, link_filter=None):
@@ -269,7 +268,7 @@ class Builder:
             If the filter returns False, no symlink will be created. By
             default a symlink is created for everything.
         """
-        for entry in scandir.scandir(src_dir):
+        for entry in os.scandir(src_dir):
             if entry.name.endswith('~'):
                 continue
             if link_filter and not link_filter(entry.path):
@@ -334,7 +333,7 @@ class Builder:
     def do_build(self):
         # We need this to be both repeatable and in the desired order
         dir_list = sorted(
-            scandir.scandir(self.src_dir),
+            os.scandir(self.src_dir),
             key=lambda x: x.name.lower(),
             reverse=True)
         for entry in dir_list:
diff --git a/lib/lp/services/config/fixture.py b/lib/lp/services/config/fixture.py
index 9be9e0b..698ceea 100644
--- a/lib/lp/services/config/fixture.py
+++ b/lib/lp/services/config/fixture.py
@@ -18,7 +18,6 @@ import shutil
 from textwrap import dedent
 
 from fixtures import Fixture
-import scandir
 
 from lp.services.config import config
 
@@ -125,7 +124,7 @@ class ConfigFixture(Fixture):
         self.absroot = os.path.abspath(root)
         self.addCleanup(shutil.rmtree, self.absroot)
         source = os.path.join(config.root, 'configs', self.copy_from_instance)
-        for entry in scandir.scandir(source):
+        for entry in os.scandir(source):
             if entry.name == 'launchpad-lazr.conf':
                 self.add_section(self._extend_str % self.copy_from_instance)
                 continue
diff --git a/lib/lp/services/config/tests/test_config.py b/lib/lp/services/config/tests/test_config.py
index 519d634..2906e4b 100644
--- a/lib/lp/services/config/tests/test_config.py
+++ b/lib/lp/services/config/tests/test_config.py
@@ -18,7 +18,6 @@ import unittest
 from fixtures import TempDir
 from lazr.config import ConfigSchema
 from lazr.config.interfaces import ConfigErrors
-import scandir
 import testtools
 
 import lp.services.config
@@ -136,7 +135,7 @@ def test_suite():
     load_testcase = unittest.defaultTestLoader.loadTestsFromTestCase
     # Add a test for every launchpad[.lazr].conf file in our tree.
     for config_dir in lp.services.config.CONFIG_ROOT_DIRS:
-        for dirpath, dirnames, filenames in scandir.walk(config_dir):
+        for dirpath, dirnames, filenames in os.walk(config_dir):
             if os.path.basename(dirpath) in EXCLUDED_CONFIGS:
                 del dirnames[:]  # Don't look in subdirectories.
                 continue
diff --git a/lib/lp/services/librarianserver/librariangc.py b/lib/lp/services/librarianserver/librariangc.py
index 71c3355..936ef3a 100644
--- a/lib/lp/services/librarianserver/librariangc.py
+++ b/lib/lp/services/librarianserver/librariangc.py
@@ -20,7 +20,6 @@ from time import time
 
 import iso8601
 import pytz
-import scandir
 import six
 from swiftclient import client as swiftclient
 from zope.interface import implementer
@@ -662,8 +661,8 @@ def delete_unwanted_disk_files(con):
     hex_content_id_re = re.compile(r'^([0-9a-f]{8})(\.migrated)?$')
     ONE_DAY = 24 * 60 * 60
 
-    for dirpath, dirnames, filenames in scandir.walk(
-        get_storage_root(), followlinks=True):
+    for dirpath, dirnames, filenames in os.walk(
+            get_storage_root(), followlinks=True):
 
         # Ignore known and harmless noise in the Librarian storage area.
         if 'incoming' in dirnames:
diff --git a/lib/lp/services/librarianserver/swift.py b/lib/lp/services/librarianserver/swift.py
index 3f0c6eb..5460448 100644
--- a/lib/lp/services/librarianserver/swift.py
+++ b/lib/lp/services/librarianserver/swift.py
@@ -21,7 +21,6 @@ import os.path
 import re
 import time
 
-import scandir
 import six
 from six.moves.urllib.parse import quote
 from swiftclient import client as swiftclient
@@ -90,8 +89,7 @@ def to_swift(log, start_lfc_id=None, end_lfc_id=None,
     # Walk the Librarian on disk file store, searching for matching
     # files that may need to be copied into Swift. We need to follow
     # symlinks as they are being used span disk partitions.
-    for dirpath, dirnames, filenames in scandir.walk(
-            fs_root, followlinks=True):
+    for dirpath, dirnames, filenames in os.walk(fs_root, followlinks=True):
 
         # Don't recurse if we know this directory contains no matching
         # files.
diff --git a/lib/lp/services/mail/mailbox.py b/lib/lp/services/mail/mailbox.py
index 5ec49fe..2118176 100644
--- a/lib/lp/services/mail/mailbox.py
+++ b/lib/lp/services/mail/mailbox.py
@@ -16,7 +16,6 @@ import poplib
 import socket
 import threading
 
-import scandir
 from zope.interface import (
     implementer,
     Interface,
@@ -170,7 +169,7 @@ class DirectoryMailBox:
 
     def items(self):
         """See IMailBox."""
-        for entry in scandir.scandir(self.mail_dir):
+        for entry in os.scandir(self.mail_dir):
             if entry.is_file():
                 with open(entry.path, "rb") as mail_file:
                     yield (entry.path, mail_file.read())
diff --git a/lib/lp/services/scripts/tests/__init__.py b/lib/lp/services/scripts/tests/__init__.py
index 8ca2036..3bd81ba 100644
--- a/lib/lp/services/scripts/tests/__init__.py
+++ b/lib/lp/services/scripts/tests/__init__.py
@@ -10,8 +10,6 @@ __all__ = [
 import os
 import subprocess
 
-import scandir
-
 import lp
 from lp.services.config import config
 
@@ -34,7 +32,7 @@ def find_lp_scripts():
     scripts = []
     for script_location in SCRIPT_LOCATIONS:
         location = os.path.join(LP_TREE, script_location)
-        for path, dirs, filenames in scandir.walk(location):
+        for path, dirs, filenames in os.walk(location):
             for filename in filenames:
                 script_path = os.path.join(path, filename)
                 if (filename.startswith('_') or
diff --git a/lib/lp/services/testing/__init__.py b/lib/lp/services/testing/__init__.py
index 0cc4713..41ede34 100644
--- a/lib/lp/services/testing/__init__.py
+++ b/lib/lp/services/testing/__init__.py
@@ -21,8 +21,6 @@ import logging
 import os
 import unittest
 
-import scandir
-
 from lp.testing.systemdocs import (
     LayeredDocFileSuite,
     setUp,
@@ -100,7 +98,7 @@ def build_test_suite(base_dir, special_tests={},
     if os.path.exists(stories_path):
         suite.addTest(PageTestSuite(
             stories_dir, package, setUp=pageTestsSetUp))
-        for story_entry in scandir.scandir(stories_path):
+        for story_entry in os.scandir(stories_path):
             if not story_entry.is_dir():
                 continue
             story_path = os.path.join(stories_dir, story_entry.name)
diff --git a/lib/lp/services/webservice/tests/test_wadllib.py b/lib/lp/services/webservice/tests/test_wadllib.py
index bda0f27..382942c 100644
--- a/lib/lp/services/webservice/tests/test_wadllib.py
+++ b/lib/lp/services/webservice/tests/test_wadllib.py
@@ -9,7 +9,6 @@ __all__ = ['test_suite']
 import os
 import unittest
 
-import scandir
 import wadllib
 
 from lp.testing.systemdocs import LayeredDocFileSuite
@@ -23,7 +22,7 @@ def test_suite():
 
     # Find all the doctests in wadllib.
     packages = []
-    for dirpath, dirnames, filenames in scandir.walk(topdir):
+    for dirpath, dirnames, filenames in os.walk(topdir):
         if 'docs' in dirnames:
             docsdir = os.path.join(dirpath, 'docs')[len(topdir) + 1:]
             packages.append(docsdir)
diff --git a/lib/lp/soyuz/doc/soyuz-set-of-uploads.txt b/lib/lp/soyuz/doc/soyuz-set-of-uploads.txt
index 5dcf121..92a08af 100644
--- a/lib/lp/soyuz/doc/soyuz-set-of-uploads.txt
+++ b/lib/lp/soyuz/doc/soyuz-set-of-uploads.txt
@@ -116,12 +116,11 @@ Firstly, we need a way to copy a test upload into the queue (but skip
 lock files, which have names starting with a dot).
 
     >>> import shutil
-    >>> import scandir
     >>> from lp.archiveuploader.tests import datadir
     >>> def punt_upload_into_queue(leaf, distro):
     ...     inc_dir = os.path.join(incoming_dir, leaf, distro)
     ...     os.makedirs(inc_dir)
-    ...     for entry in scandir.scandir(datadir(os.path.join("suite", leaf))):
+    ...     for entry in os.scandir(datadir(os.path.join("suite", leaf))):
     ...         shutil.copy(entry.path, inc_dir)
 
 We need a way to count the items in a queue directory
@@ -614,7 +613,6 @@ release files in the way bug-54039 infected code would.
 
 First a couple helpers.
 
-    >>> import os
     >>> import stat
     >>> from lp.testing.script import run_script
 
diff --git a/lib/lp/soyuz/doc/soyuz-upload.txt b/lib/lp/soyuz/doc/soyuz-upload.txt
index bace025..6f049e9 100644
--- a/lib/lp/soyuz/doc/soyuz-upload.txt
+++ b/lib/lp/soyuz/doc/soyuz-upload.txt
@@ -101,10 +101,9 @@ files match the uploaded ones.
     ...     with open(filename, 'rb') as f:
     ...         return hashlib.md5(f.read()).digest()
 
-    >>> import scandir
     >>> def get_upload_dir(num, dir=incoming_dir):
     ...     """Return the path to the upload, if found in the dir."""
-    ...     for upload_entry in scandir.scandir(dir):
+    ...     for upload_entry in os.scandir(dir):
     ...         if upload_entry.name.endswith("%06d" % num):
     ...             return upload_entry.path
     ...     return None
diff --git a/lib/lp/soyuz/scripts/gina/packages.py b/lib/lp/soyuz/scripts/gina/packages.py
index 5feb27c..646b060 100644
--- a/lib/lp/soyuz/scripts/gina/packages.py
+++ b/lib/lp/soyuz/scripts/gina/packages.py
@@ -28,7 +28,6 @@ import re
 import shutil
 import tempfile
 
-import scandir
 import six
 
 from lp.app.validators.version import valid_debian_version
@@ -92,7 +91,7 @@ def get_dsc_path(name, version, component, archive_root):
         return filename, fullpath, component
 
     # Do a second pass, scrubbing through all components in the pool.
-    for alt_component_entry in scandir.scandir(pool_root):
+    for alt_component_entry in os.scandir(pool_root):
         if not alt_component_entry.is_dir():
             continue
         pool_dir = poolify(name, alt_component_entry.name)
diff --git a/lib/lp/soyuz/tests/fakepackager.py b/lib/lp/soyuz/tests/fakepackager.py
index 8106d50..9500ac8 100644
--- a/lib/lp/soyuz/tests/fakepackager.py
+++ b/lib/lp/soyuz/tests/fakepackager.py
@@ -18,7 +18,6 @@ import tarfile
 import tempfile
 import time
 
-import scandir
 from zope.component import getUtility
 
 from lp.archiveuploader.nascentupload import NascentUpload
@@ -377,7 +376,7 @@ class FakePackager:
 
     def listAvailableUploads(self):
         """Return the path for all available changesfiles."""
-        changes = [entry.path for entry in scandir.scandir(self.sandbox_path)
+        changes = [entry.path for entry in os.scandir(self.sandbox_path)
                    if entry.name.endswith('.changes')]
 
         return sorted(changes)
diff --git a/lib/lp/soyuz/tests/test_doc.py b/lib/lp/soyuz/tests/test_doc.py
index 0096084..dc1b5eb 100644
--- a/lib/lp/soyuz/tests/test_doc.py
+++ b/lib/lp/soyuz/tests/test_doc.py
@@ -9,7 +9,6 @@ import logging
 import os
 import unittest
 
-import scandir
 import transaction
 
 from lp.services.config import config
@@ -167,7 +166,7 @@ def test_suite():
     stories_dir = os.path.join(os.path.pardir, 'stories')
     suite.addTest(PageTestSuite(stories_dir))
     stories_path = os.path.join(here, stories_dir)
-    for story_entry in scandir.scandir(stories_path):
+    for story_entry in os.scandir(stories_path):
         if not story_entry.is_dir():
             continue
         story_path = os.path.join(stories_dir, story_entry.name)
diff --git a/lib/lp/soyuz/tests/test_publishing.py b/lib/lp/soyuz/tests/test_publishing.py
index 1d4a1b5..d3d085e 100644
--- a/lib/lp/soyuz/tests/test_publishing.py
+++ b/lib/lp/soyuz/tests/test_publishing.py
@@ -11,7 +11,6 @@ import shutil
 import tempfile
 
 import pytz
-import scandir
 from storm.store import Store
 from testtools.matchers import Equals
 import transaction
@@ -501,7 +500,7 @@ class SoyuzTestPublisher:
 
     def _findChangesFile(self, top, name_fragment):
         """File with given name fragment in directory tree starting at top."""
-        for root, dirs, files in scandir.walk(top, topdown=False):
+        for root, dirs, files in os.walk(top, topdown=False):
             for name in files:
                 if (name.endswith('.changes') and
                     name.find(name_fragment) > -1):
diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py
index 8fbd261..b74f3b9 100644
--- a/lib/lp/testing/__init__.py
+++ b/lib/lp/testing/__init__.py
@@ -82,7 +82,6 @@ from lazr.restful.testing.webservice import FakeRequest
 import lp_sitecustomize
 import oops_datedir_repo.serializer_rfc822
 import pytz
-import scandir
 import simplejson
 import six
 from storm.store import Store
@@ -1162,7 +1161,7 @@ def build_yui_unittest_suite(app_testing_path, yui_test_class):
 
 
 def _harvest_yui_test_files(file_path):
-    for dirpath, dirnames, filenames in scandir.walk(file_path):
+    for dirpath, dirnames, filenames in os.walk(file_path):
         for filename in filenames:
             if fnmatchcase(filename, "test_*.html"):
                 yield os.path.join(dirpath, filename)
diff --git a/lib/lp/testing/gpgkeys/__init__.py b/lib/lp/testing/gpgkeys/__init__.py
index a1d0378..8387e33 100644
--- a/lib/lp/testing/gpgkeys/__init__.py
+++ b/lib/lp/testing/gpgkeys/__init__.py
@@ -23,7 +23,6 @@ from io import BytesIO
 import os
 
 import gpgme
-import scandir
 import six
 from zope.component import getUtility
 
@@ -113,7 +112,7 @@ def test_pubkey_from_email(email_addr):
 
 def test_keyrings():
     """Iterate over the filenames for test keyrings."""
-    for entry in scandir.scandir(gpgkeysdir):
+    for entry in os.scandir(gpgkeysdir):
         if entry.name.endswith('.gpg'):
             yield entry.path
 
diff --git a/lib/lp/testing/yuixhr.py b/lib/lp/testing/yuixhr.py
index f66295b..beb8c07 100644
--- a/lib/lp/testing/yuixhr.py
+++ b/lib/lp/testing/yuixhr.py
@@ -20,7 +20,6 @@ import unittest
 
 from lazr.restful import ResourceJSONEncoder
 from lazr.restful.utils import get_current_browser_request
-import scandir
 import simplejson
 from six.moves import reload_module
 from zope.component import getUtility
@@ -451,7 +450,7 @@ class YUITestFixtureControllerView(LaunchpadView):
 
 
 def find_tests(root):
-    for dirpath, dirnames, filenames in scandir.walk(root):
+    for dirpath, dirnames, filenames in os.walk(root):
         dirpath = os.path.relpath(dirpath, root)
         for filename in filenames:
             if fnmatchcase(filename, 'test_*.js'):
diff --git a/lib/lp/testopenid/stories/tests.py b/lib/lp/testopenid/stories/tests.py
index 0c0d88e..88034e9 100644
--- a/lib/lp/testopenid/stories/tests.py
+++ b/lib/lp/testopenid/stories/tests.py
@@ -4,8 +4,6 @@
 import os
 import unittest
 
-import scandir
-
 from lp.testing.pages import PageTestSuite
 
 
@@ -14,7 +12,7 @@ here = os.path.dirname(os.path.realpath(__file__))
 
 def test_suite():
     stories = sorted(
-        entry.name for entry in scandir.scandir(here)
+        entry.name for entry in os.scandir(here)
         if not entry.name.startswith('.') and entry.is_dir())
 
     suite = unittest.TestSuite()
diff --git a/lib/lp/tests/test_opensource.py b/lib/lp/tests/test_opensource.py
index 11fefc5..a3d67ef 100644
--- a/lib/lp/tests/test_opensource.py
+++ b/lib/lp/tests/test_opensource.py
@@ -21,7 +21,6 @@ import os
 import unittest
 
 import launchpadlib
-import scandir
 import wadllib
 
 from lp.testing.layers import AppServerLayer
@@ -33,7 +32,7 @@ def add_testable_opensource_package(suite, package):
     topdir = os.path.dirname(package.__file__)
 
     packages = []
-    for dirpath, dirnames, filenames in scandir.walk(topdir):
+    for dirpath, dirnames, filenames in os.walk(topdir):
         if 'docs' in dirnames:
             docsdir = os.path.join(dirpath, 'docs')[len(topdir) + 1:]
             packages.append(docsdir)
diff --git a/lib/lp/translations/tests/test_doc.py b/lib/lp/translations/tests/test_doc.py
index 9229590..2ec46ee 100644
--- a/lib/lp/translations/tests/test_doc.py
+++ b/lib/lp/translations/tests/test_doc.py
@@ -9,8 +9,6 @@ import logging
 import os
 import unittest
 
-import scandir
-
 from lp.testing.layers import (
     LaunchpadFunctionalLayer,
     LaunchpadZopelessLayer,
@@ -62,7 +60,7 @@ def test_suite():
     stories_dir = os.path.join(os.path.pardir, 'stories')
     suite.addTest(PageTestSuite(stories_dir))
     stories_path = os.path.join(here, stories_dir)
-    for story_entry in scandir.scandir(stories_path):
+    for story_entry in os.scandir(stories_path):
         if not story_entry.is_dir():
             continue
         story_path = os.path.join(stories_dir, story_entry.name)
diff --git a/requirements/launchpad.txt b/requirements/launchpad.txt
index e05a33f..12a04b1 100644
--- a/requirements/launchpad.txt
+++ b/requirements/launchpad.txt
@@ -128,7 +128,6 @@ requests-file==1.4.3
 requests-toolbelt==0.9.1
 responses==0.9.0
 s3transfer==0.3.6
-scandir==1.7
 secure-cookie==0.1.0
 service-identity==18.1.0
 setproctitle==1.1.7
diff --git a/scripts/migrate-librarian-content-md5.py b/scripts/migrate-librarian-content-md5.py
index 9caaf62..0031a73 100755
--- a/scripts/migrate-librarian-content-md5.py
+++ b/scripts/migrate-librarian-content-md5.py
@@ -13,8 +13,6 @@ import os
 import subprocess
 import sys
 
-import scandir
-
 
 SQL = "UPDATE LibraryFileContent SET md5 = '%s' WHERE id = %d;"
 
@@ -23,7 +21,7 @@ def main(path, minimumID=0):
     if not path.endswith('/'):
         path += '/'
 
-    for dirpath, dirname, filenames in scandir.walk(path):
+    for dirpath, dirname, filenames in os.walk(path):
         dirname.sort()
         databaseID = dirpath[len(path):]
         if not len(databaseID) == 8:  # "xx/xx/xx"
diff --git a/setup.py b/setup.py
index c10e833..6e945a8 100644
--- a/setup.py
+++ b/setup.py
@@ -214,7 +214,6 @@ setup(
         'requests-file',
         'requests-toolbelt',
         'responses',
-        'scandir',
         'secure-cookie',
         'setproctitle',
         'setuptools',