Unrud 7 лет назад
Родитель
Сommit
a5fa35e785
4 измененных файлов с 23 добавлено и 59 удалено
  1. 0 4
      .travis.yml
  2. 6 16
      radicale/server.py
  3. 15 36
      radicale/storage.py
  4. 2 3
      setup.py

+ 0 - 4
.travis.yml

@@ -3,10 +3,6 @@ sudo: false
 
 matrix:
   include:
-    - os: linux
-      python: 3.3
-    - os: linux
-      python: 3.4
     - os: linux
       python: 3.5
     - os: linux

+ 6 - 16
radicale/server.py

@@ -73,10 +73,6 @@ class HTTPServer(wsgiref.simple_server.WSGIServer):
                 self.server_close()
                 raise
 
-        if self.client_timeout and sys.version_info < (3, 5, 2):
-            logger.warning("Using server.timeout with Python < 3.5.2 "
-                           "can cause network connection failures")
-
     def get_request(self):
         # Set timeout for client
         _socket, address = super().get_request()
@@ -249,12 +245,8 @@ def serve(configuration):
                     if configuration.getboolean("server", "ssl") else "")
 
     # Create a socket pair to notify the select syscall of program shutdown
-    # This is not available in python < 3.5 on Windows
-    if hasattr(socket, "socketpair"):
-        shutdown_program_socket_in, shutdown_program_socket_out = (
-            socket.socketpair())
-    else:
-        shutdown_program_socket_in, shutdown_program_socket_out = None, None
+    shutdown_program_socket_in, shutdown_program_socket_out = (
+        socket.socketpair())
 
     # SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for
     # shutdown
@@ -265,18 +257,16 @@ def serve(configuration):
             return
         logger.info("Stopping Radicale")
         shutdown_program = True
-        if shutdown_program_socket_in:
-            shutdown_program_socket_in.sendall(b"goodbye")
+        shutdown_program_socket_in.sendall(b" ")
     signal.signal(signal.SIGTERM, shutdown)
     signal.signal(signal.SIGINT, shutdown)
 
     # Main loop: wait for requests on any of the servers or program shutdown
     sockets = list(servers.keys())
-    if shutdown_program_socket_out:
-        # Use socket pair to get notified of program shutdown
-        sockets.append(shutdown_program_socket_out)
+    # Use socket pair to get notified of program shutdown
+    sockets.append(shutdown_program_socket_out)
     select_timeout = None
-    if not shutdown_program_socket_out or os.name == "nt":
+    if os.name == "nt":
         # Fallback to busy waiting. (select.select blocks SIGINT on Windows.)
         select_timeout = 1.0
     logger.info("Radicale server ready")

+ 15 - 36
radicale/storage.py

@@ -34,7 +34,6 @@ import pickle
 import posixpath
 import shlex
 import subprocess
-import sys
 import threading
 import time
 from contextlib import contextmanager
@@ -47,12 +46,9 @@ from tempfile import NamedTemporaryFile, TemporaryDirectory
 
 import vobject
 
+from radicale import xmlutils
 from radicale.log import logger
 
-if sys.version_info >= (3, 5):
-    # HACK: Avoid import cycle for Python < 3.5
-    from radicale import xmlutils
-
 if os.name == "nt":
     import ctypes
     import ctypes.wintypes
@@ -97,10 +93,6 @@ INTERNAL_TYPES = ("multifilesystem",)
 
 def load(configuration):
     """Load the storage manager chosen in configuration."""
-    if sys.version_info < (3, 5):
-        # HACK: Avoid import cycle for Python < 3.5
-        global xmlutils
-        from radicale import xmlutils
     storage_type = configuration.get("storage", "type")
     if storage_type == "multifilesystem":
         collection_class = Collection
@@ -241,27 +233,6 @@ def find_available_name(exists_fn, suffix=""):
     raise RuntimeError("No unique random sequence found")
 
 
-def scandir(path, only_dirs=False, only_files=False):
-    """Iterator for directory elements. (For compatibility with Python < 3.5)
-
-    ``only_dirs`` only return directories
-
-    ``only_files`` only return files
-
-    """
-    if sys.version_info >= (3, 5):
-        for entry in os.scandir(path):
-            if ((not only_files or entry.is_file()) and
-                    (not only_dirs or entry.is_dir())):
-                yield entry.name
-    else:
-        for name in os.listdir(path):
-            p = os.path.join(path, name)
-            if ((not only_files or os.path.isfile(p)) and
-                    (not only_dirs or os.path.isdir(p))):
-                yield name
-
-
 def get_etag(text):
     """Etag from collection or item.
 
@@ -354,7 +325,8 @@ def path_to_filesystem(root, *paths):
             # Check for conflicting files (e.g. case-insensitive file systems
             # or short names on Windows file systems)
             if (os.path.lexists(safe_path) and
-                    part not in scandir(safe_path_parent)):
+                    part not in (e.name for e in
+                                 os.scandir(safe_path_parent))):
                 raise CollidingPathError(part)
     return safe_path
 
@@ -899,7 +871,10 @@ class Collection(BaseCollection):
             with child_context_manager(sane_path, href):
                 yield collection.get(href)
 
-        for href in scandir(filesystem_path, only_dirs=True):
+        for entry in os.scandir(filesystem_path):
+            if not entry.is_dir():
+                continue
+            href = entry.name
             if not is_safe_filesystem_path_component(href):
                 if not href.startswith(".Radicale"):
                     logger.debug("Skipping collection %r in %r",
@@ -1174,7 +1149,8 @@ class Collection(BaseCollection):
         history_folder = os.path.join(self._filesystem_path,
                                       ".Radicale.cache", "history")
         try:
-            for href in scandir(history_folder):
+            for entry in os.scandir(history_folder):
+                href = entry.name
                 if not is_safe_filesystem_path_component(href):
                     continue
                 if os.path.isfile(os.path.join(self._filesystem_path, href)):
@@ -1285,7 +1261,10 @@ class Collection(BaseCollection):
         return token, changes
 
     def list(self):
-        for href in scandir(self._filesystem_path, only_files=True):
+        for entry in os.scandir(self._filesystem_path):
+            if not entry.is_file():
+                continue
+            href = entry.name
             if not is_safe_filesystem_path_component(href):
                 if not href.startswith(".Radicale"):
                     logger.debug("Skipping item %r in %r", href, self.path)
@@ -1357,8 +1336,8 @@ class Collection(BaseCollection):
         cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache",
                                     "item")
         self._clean_cache(cache_folder, (
-            href for href in scandir(cache_folder) if not
-            os.path.isfile(os.path.join(self._filesystem_path, href))))
+            e.name for e in os.scandir(cache_folder) if not
+            os.path.isfile(os.path.join(self._filesystem_path, e.name))))
 
     def _get_with_metadata(self, href, verify_href=True):
         """Like ``get`` but additonally returns the following metadata:

+ 2 - 3
setup.py

@@ -74,7 +74,7 @@ setup(
         "md5": "passlib",
         "bcrypt": "passlib[bcrypt]"},
     keywords=["calendar", "addressbook", "CalDAV", "CardDAV"],
-    python_requires=">=3.3",
+    python_requires=">=3.5.2",
     classifiers=[
         "Development Status :: 5 - Production/Stable",
         "Environment :: Console",
@@ -84,8 +84,7 @@ setup(
         "License :: OSI Approved :: GNU General Public License (GPL)",
         "Operating System :: OS Independent",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.3",
-        "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
         "Topic :: Office/Business :: Groupware"])