summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhiQiang Fan <aji.zqfan@gmail.com>2015-11-28 02:17:05 +0800
committerZhiQiang Fan <aji.zqfan@gmail.com>2015-12-12 10:58:55 +0800
commite46a46ba90741987f1147afc56876e3d0d27e8a2 (patch)
treea81fa40db81ccee482ef46b1d0f4ae72452f8244
parentd423b3b74cfae284c4b46d3bad5232e3840e05f3 (diff)
downloadoslo-utils-e46a46ba90741987f1147afc56876e3d0d27e8a2.tar.gz
re-implement thread safe fnmatch
fnmatch is not thread safe for versions <= 2.7.9. We have used it in some projects without any lock for concurrency scenario. This patch re-implements a thread safe fnmatch which is very similar to standard nmatch. Change-Id: I610ffcdf58d1590b8e0c0faefd8832563b33ab30 ref: https://bugs.python.org/issue23191
-rw-r--r--oslo_utils/fnmatch.py76
-rw-r--r--oslo_utils/tests/test_fnmatch.py30
2 files changed, 106 insertions, 0 deletions
diff --git a/oslo_utils/fnmatch.py b/oslo_utils/fnmatch.py
new file mode 100644
index 0000000..8dd7aa0
--- /dev/null
+++ b/oslo_utils/fnmatch.py
@@ -0,0 +1,76 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Thread safe fnmatch re-implementation.
+
+Standard library fnmatch in Python versions <= 2.7.9 has thread safe
+issue, this module is created for such case. see:
+https://bugs.python.org/issue23191
+"""
+
+from __future__ import absolute_import
+
+import fnmatch as standard_fnmatch
+import os
+import posixpath
+import re
+import sys
+
+
+if sys.version_info > (2, 7, 9):
+ fnmatch = standard_fnmatch.fnmatch
+ fnmatchcase = standard_fnmatch.fnmatchcase
+ filter = standard_fnmatch.filter
+ translate = standard_fnmatch.translate
+else:
+ _MATCH_CACHE = {}
+ _MATCH_CACHE_MAX = 100
+
+ translate = standard_fnmatch.translate
+
+ def _get_cached_pattern(pattern):
+ cached_pattern = _MATCH_CACHE.get(pattern)
+ if cached_pattern is None:
+ translated_pattern = translate(pattern)
+ cached_pattern = re.compile(translated_pattern)
+ if len(_MATCH_CACHE) >= _MATCH_CACHE_MAX:
+ _MATCH_CACHE.clear()
+ _MATCH_CACHE[pattern] = cached_pattern
+ return cached_pattern
+
+ def fnmatchcase(filename, pattern):
+ cached_pattern = _get_cached_pattern(pattern)
+ return cached_pattern.match(filename) is not None
+
+ def fnmatch(filename, pattern):
+ filename = os.path.normcase(filename)
+ pattern = os.path.normcase(pattern)
+ return fnmatchcase(filename, pattern)
+
+ def filter(filenames, pattern):
+ filtered_filenames = []
+
+ pattern = os.path.normcase(pattern)
+ cached_pattern = _get_cached_pattern(pattern)
+
+ if os.path is posixpath:
+ # normcase on posix is NOP. Optimize it away from the loop.
+ for filename in filenames:
+ if cached_pattern.match(filename):
+ filtered_filenames.append(filename)
+ else:
+ for filename in filenames:
+ filename = os.path.normcase(filename)
+ if cached_pattern.match(filename):
+ filtered_filenames.append(filename)
+
+ return filtered_filenames
diff --git a/oslo_utils/tests/test_fnmatch.py b/oslo_utils/tests/test_fnmatch.py
new file mode 100644
index 0000000..537d72a
--- /dev/null
+++ b/oslo_utils/tests/test_fnmatch.py
@@ -0,0 +1,30 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import sys
+
+import mock
+from oslotest import base
+
+from oslo_utils import fnmatch
+
+
+class TestFnmatch(base.BaseTestCase):
+
+ @mock.patch.object(sys, 'version_info', new=(2, 7, 0))
+ def test_fnmatch(self):
+ self.assertFalse(fnmatch.fnmatch("tesX", "Test"))
+ self.assertTrue(fnmatch.fnmatch("test", "test"))
+ self.assertFalse(fnmatch.fnmatchcase("test", "Test"))
+ self.assertTrue(fnmatch.fnmatchcase("test", "test"))
+ self.assertTrue(fnmatch.fnmatch("testX", "test*"))
+ self.assertEqual(["test"], fnmatch.filter(["test", "testX"], "test"))